Help us shape the future of Go! Take the 2026 Go Developer Survey

tela

package module
v0.5.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 14, 2026 License: BSD-3-Clause Imports: 4 Imported by: 0

README

Tela

Go Reference

A desktop UI framework in pure Go.
No cgo, no webview, no native toolkits. Tailwind classes, React-style hooks.

Tela draws every pixel itself, like Flutter or a browser, with its own rasterizer, text layout and flexbox engine. You write function components with hooks and style them with the Tailwind v4 vocabulary; the same tree renders to a window on Linux or, with no window at all, to a PNG you can look at and a text tree you can read. That last part is on purpose: Tela is designed so both people and LLM agents can build good-looking interfaces without trial and error.

Status: pre-1.0 (v0.5.0). Phases 1–5 are done and tagged: Linux (Wayland, X11) and Windows windows, text inputs and IME. macOS is not there yet. The public API (tela, h) may still break between minor versions. See Project status.

Contents

Why Tela

  • Pure Go, always CGO_ENABLED=0. Linux talks the Wayland and X11 protocols over Unix sockets, Windows calls the system DLLs through syscall, and the macOS backend (planned) will use the Objective-C runtime without cgo. One go build, one static binary, easy cross-compilation.
  • Known vocabulary. Names copy React (UseState, UseEffect, Key, OnClick) and classes copy Tailwind v4 verbatim. If an LLM would write it in React + Tailwind, it works here.
  • Real CSS semantics. Block and flex layout checked against 828 Taffy fixtures and Chromium; box painting, gradients, shadows and blur compared to Chromium's pixels; text shaped with HarfBuzz (bidi, Arabic, CJK, color emoji).
  • Deterministic. Embedded fonts and an own rasterizer: same tree, same pixels on every platform and architecture. Goldens are bit-exact on amd64 and arm64.
  • Fast when it matters, silent when it doesn't. Frames on demand, 0% CPU when idle, layers with exact damage tracking, and phase-deferred values that repaint or relayout one element without rebuilding anything. A 100,000-row VirtualList scrolls with a p95 of 2.3 ms per frame on the CPU rasterizer.
  • Observable. Everything renders headlessly and can be inspected as an aria snapshot or a layout tree. Errors teach: unknown class "rounded-xxl"; did you mean "rounded-2xl"?.

A first look

package main

import (
	"github.com/kuronosu/tela"
	. "github.com/kuronosu/tela/h"
)

func Counter() tela.Node {
	count, setCount := tela.UseState(0)

	return Div(Class("flex flex-col items-center gap-6 p-10 rounded-3xl "+
		"bg-linear-to-br from-indigo-500 to-fuchsia-600 shadow-2xl"),
		H1(Class("text-5xl font-bold text-white tracking-tight"),
			Textf("Count: %d", count)),
		Button(Class("px-6 py-3 rounded-xl bg-white/15 text-white backdrop-blur "+
			"hover:bg-white/25 active:scale-95 transition"),
			OnClick(func(tela.MouseEvent) { setCount.Set(count + 1) }),
			Text("Increment"),
		),
	)
}

func main() {
	tela.Run(tela.App{Title: "Demo", Width: 800, Height: 600, Root: Counter})
}

The Counter example rendered by Tela

This is examples/counter, and it is the one that anchors the project: its render matches the same markup written in HTML + Tailwind CSS and drawn by Chromium 152, with every box edge within 0.01 px and pixels equal except glyph antialiasing.

Quick start

You need Go 1.27 or newer, and on Linux a Wayland or X11 session.

git clone https://github.com/kuronosu/tela
cd tela

# Open a window
CGO_ENABLED=0 go run ./examples/counter

# Or render without a window, to a PNG
CGO_ENABLED=0 go run ./cmd/tela snapshot --size 480x200 --scale 2 \
    -o counter.png ./examples/counter

# Print the screen as text (an aria snapshot, in YAML)
CGO_ENABLED=0 go run ./cmd/tela inspect --size 800x600 ./examples/counter

The inspect output for the counter is what a screen reader, a test or an agent sees:

- 'heading "Count: 0" [level=1]'
- button "Increment"

The tela command runs a package's own code, so it needs the go command and a package in a module that requires Tela (see cmd/tela).

Debug overlays

TELA_DEBUG=layout,paint-flash,layers,fps,semantics draws diagnostics on top of a running app with Tela's own renderer, at no cost when off. TELA_DEV=1 enables dev-mode checks such as hook-order errors that print both call sites.

Examples

Every screen lives in examples/, with its own README, goldens and tests. They are the best way to see what the framework does today.

Dashboard

A shadcn-style admin dashboard: tabs, stat cards, a chart built from divs, group-hover: reveals and dark mode. Small tela.CP components skip their build when their props do not change.

dashboard example, light dashboard example, dark

Dark mode is a class pair (bg-white dark:bg-zinc-900); render it with tela snapshot --dark.

Signup form

A "Create your account" screen with every text field Tela has: h.Input with Type("email"), invalid: and peer-invalid: styling, a password with a show/hide toggle, an auto-growing h.Textarea with a live counter (BindValue + TextFn), h.Label, implicit submission with Enter, and a submit button that stays disabled until the form is valid.

signup example, with an invalid e-mail

More screens

pricing: a monthly/yearly switch, a highlighted gradient plan, absolute badges.
pricing example
settings: a whole form as one comparable value, switches, a theme picker, a dirty flag.
settings example
player: a progress bar that advances ten times a second without building anything (UseValue + WidthFn).
player example
board: a kanban board with drag and drop between columns, using OnDrag and TranslateFn.
board example
shapes: rounded cards, gradients, analytic shadows, blur and glass.
shapes example
text: mixed-script paragraphs (Latin, Arabic, CJK, emoji), selection and carets.
text example

Native look

native: one file-explorer tree that looks native on Windows 11, GNOME and KDE, each in light and dark. A theme is a set of class strings (bg-[#cce8ff], rounded-[8px]), a column order and some number formats; -theme= and -dark= choose the look, and -dark=auto follows the system.

native example, Windows 11 native example, GNOME native example, KDE

Also there: hello, the screen at the top of this page; and virtuallist, 100,000 people in one h.VirtualList with variable row heights and ScrollToIndex.

Styling

Classes are Tailwind v4's, generated from its theme and parsed into a typed ComputedStyle: spacing, sizing, colors with /NN opacity, borders, radius, shadows, gradients, typography, flex, overflow, transforms, arbitrary values (w-[calc(50%-8px)]) and variants (hover:, active:, focus:, focus-visible:, disabled:, group-*, peer-*, dark:, md:/lg:). Typed helpers cover what a class cannot, such as values that change every frame.

State and hooks

UseState, UseReducer, UseEffect, UseLayoutEffect, UseMemo, UseCallback, UseRef, UseContext, UseId, plus tela.UseValue for values that should repaint or relayout one element without rebuilding the component that owns them. Component boundaries tela.C, tela.CP and tela.Memo skip work when props are comparable and equal.

type RowProps struct{ Index int }

func Row(p RowProps) tela.Node { /* ... */ }

// Only rows that enter the viewport are built; a scroll that stays inside the
// built rows only repaints.
VirtualList(100_000, func(int) float32 { return 65 }, func(i int) tela.Node {
	return tela.CP(Row, RowProps{Index: i})
})

Testing without a window

telatest mounts a component with no window, simulates input and compares pixels or semantics. It finds elements the way an assistive technology does:

func TestIncrement(t *testing.T) {
	app := telatest.Mount(t, tela.C(Counter), telatest.Size(800, 600), telatest.Dev())

	app.Click(telatest.ByRole("button", "Increment"))

	app.ExpectAria(`
		- 'heading "Count: 1" [level=1]'
		- button "Increment"
	`)
	app.Golden("counter_clicked.png")
}

Regenerate goldens with TELA_UPDATE=1 CGO_ENABLED=0 go test ./... and review the diff.

Tooling for agents

Because the whole pipeline is headless, a loop of write → tela snapshot → look at the PNG → tela inspect → fix needs no window and no human. Unknown classes come with suggestions, and tela inspect --layout prints boxes as JSON. Planned: tela lint, tela dev, tela mcp and a docs/llms.txt (phase 7).

Project status

Current version: v0.5.0, tagged 2026-10-13. Phases 1–5 are done.

Phase Name State
1 Pixels on screen: rasterizer, shadows, blur, Wayland and X11 windows ✅ v0.1.0
2 Text: shaping, bidi, Parley-style layout, selection, emoji ✅ v0.2.0
3 Tree, style, layout: reconciler, Tailwind classes, flex, tela snapshot ✅ v0.3.0
4 Interaction and state: hooks, pointer and keyboard, focus, scrolling, VirtualList, telatest ✅ v0.4.0
5 Windows and text editing: win32, Input/Textarea, IME, inline decorations ✅ v0.5.0
6 Advanced CSS and animation: grid, fixed/sticky, transitions, springs, backdrop-filter, breakpoints planned
7 aria hooks, kit components, tela add/lint/dev/mcp, llms.txt planned
8 macOS, optional GPU backend, accessibility, IME on X11 and macOS planned

What works today

Linux Wayland Linux X11 Headless Windows macOS
Window and rendering ✅ ✅ ✅ ✅ cross-compiles, no backend yet
Pointer, scroll, keyboard ✅ ✅ ✅ (telatest) ✅ —
Text fields, clipboard ✅ ✅ ✅ (telatest) ✅ —
IME (Japanese, Chinese, …) ✅ (text-input-v3) — (phase 8) ✅ (injected) ✅ (IMM32) —

Implemented: block, flex and relative/absolute layout; Tailwind classes and variants; shaped text with fallback; gradients, shadows, blur, transforms and opacity layers; hooks; pointer events with a gesture arena (tap, drag, long-press); focus and Tab order; scrolling with kinetic touchpad support; VirtualList; text fields (Input, Textarea) with selection, undo, clipboard and IME, Form and Label; inline decorations in paragraphs; a semantics tree; debug overlays; the tela CLI (snapshot, inspect).

Not yet, or known limits

  • No macOS window yet (phase 8); it cross-compiles. No IME on X11 yet (XIM, phase 8).
  • No checkbox, radio, select or slider elements yet; kit brings them in phase 7 (a Button with Role("checkbox") works meanwhile).
  • No CSS grid (grid lays out as block), no fixed/sticky, no transitions or animations, no backdrop-filter beyond the raster one, no background images (phase 6).
  • Large blurs are slow on CPU (examples/shapes takes ~180 ms for a full frame at 2x); unchanged glows are free thanks to damage tracking.
  • No fractional scaling on Wayland yet; no accessibility backend (AT-SPI, UIA, NSAccessibility) yet.

The full list of limitations is in docs/ROADMAP.md.

Documentation

  • API reference on pkg.go.dev, one page per public package: tela (app, hooks, events, style props), h (elements, props, helpers) and telatest (tests without a window).
  • Guides on the wiki: getting started, styling, hooks, events, forms, scrolling, accessibility, testing and the CLI, with a working example each. Its sources are versioned in docs/wiki; the API reference pages there are generated from the doc comments with go generate.
Document What it is
docs/ARCHITECTURE.md The design: layers, trees, pipeline, layout, text, input, platform layer, and the decision log (ADRs)
docs/ROADMAP.md Phases, current status, next step and session log
CHANGELOG.md User-visible changes per version
THIRD_PARTY.md Ported code and third-party licenses
examples/*/README.md What each example demonstrates

Contributing

Tela is developed in the open but is young; the design is settled in docs/ARCHITECTURE.md and changes to it go through an ADR. Please read AGENTS.md first. The hard rules are:

  • Never cgo. Everything runs with CGO_ENABLED=0 (the race detector is the only verification-only exception).
  • Pure-Go, permissively licensed dependencies from the approved list only.
  • Everything in English: code, comments, commits and docs.
  • Visual changes come with headless goldens; look at the snapshot.
CGO_ENABLED=0 go build ./...
CGO_ENABLED=0 go vet ./...
CGO_ENABLED=0 staticcheck ./...
CGO_ENABLED=0 go test ./...

# Cross-compilation must always build
CGO_ENABLED=0 GOOS=windows go build ./...
CGO_ENABLED=0 GOOS=darwin  go build ./...

License

Tela is released under the BSD 3-Clause License. Third-party code and the embedded fonts keep their own licenses; see THIRD_PARTY.md.

Documentation

Overview

Package tela is the public API of Tela, a desktop UI framework written in pure Go: no cgo, no webview, a React-like component model and Tailwind CSS styling.

A UI is a tree of Node values built by function components. Elements (Div, Button, Text...) live in the companion package h, designed to be dot-imported; this package holds the application (App, Run), hooks and component boundaries (UseState, C, CP...), events (MouseEvent, PointerEvent, DragEvent, KeyboardEvent with the Key and Code constants, FocusEvent, and the ChangeEvent, InputEvent, CompositionEvent, SelectEvent and SubmitEvent of text fields and forms), ElementRef, the typed style props for h.Style, colors, and the Previews that `tela snapshot` and `tela inspect` render.

import (
    "github.com/kuronosu/tela"
    . "github.com/kuronosu/tela/h"
)

func Counter() tela.Node {
    count, setCount := tela.UseState(0)
    return Div(Class("flex flex-col items-center gap-6 p-10"),
        H1(Class("text-5xl font-bold"), Textf("Count: %d", count)),
        Button(Class("px-6 py-3 rounded-xl bg-indigo-500 text-white"),
            OnClick(func(tela.MouseEvent) { setCount.Set(count + 1) }),
            Text("Increment")),
    )
}

func main() {
    tela.Run(tela.App{Title: "Demo", Width: 800, Height: 600, Root: Counter})
}

Styling

Static styling uses Tailwind v4 classes (h.Class). Dynamic values go through typed props, which apply after the classes:

Div(Class("rounded-xl p-4"), Style(tela.Width(w), tela.Bg(tela.Hex("#1e1e2e"))))

Values that change every frame (a drag position, a progress bar) can be deferred to a later phase with TranslateFn, OpacityFn, WidthFn and HeightFn: a UseValue read inside them redoes only that element's layout or paint when it changes. Colors are extended sRGB; see Color.

Components

A component is a function returning a Node. Wrapped in C (no props) or CP (comparable props), it becomes a component boundary: an element with its own hook state, rebuilt on its own when that state changes. CP skips the build when its props are == to the previous ones and nothing it reads changed (automatic skipping, as in Jetpack Compose); Memo does the same with a comparison function. Calling a component function directly runs it inside the caller, as a custom hook.

Div(tela.C(Header), tela.CP(Card, CardProps{Title: "Inbox"}))

Hooks

Hooks keep state and run code across builds, with React's names and semantics:

  • UseState (and UseStateSet, UseStateUpdate) and UseReducer hold state; their Setter and Dispatcher are comparable, stable and safe from any goroutine.
  • UseEffect runs code after the frame is presented and UseLayoutEffect after layout, with cleanups; UseEffect(f, nil) runs only on mount.
  • UseMemo caches a computation; UseCallback gives a stable Callback that always calls the latest function; UseRef keeps a mutable box.
  • CreateContext and UseContext pass values down the tree.
  • UseId gives a stable unique ID.
  • UseElementRef gives an ElementRef to attach with h.Ref, to focus or scroll an element from handlers and effects.
  • UseValue holds an observable Value for what changes every frame: read in the component's body, a change builds it again; read inside a deferred style closure, a change only lays out or repaints that element.
  • UseScroll gives Values that follow an element's scroll offsets, for effects tied to scrolling that repaint without rebuilding.
  • UseBounds gives a Value of an element's bounds in the window (a Rect, moved by scrolling and transforms), a ResizeObserver that also sees position; in a handler, ElementRef.Bounds reads them.

Scrolling

An element with overflow-auto or overflow-scroll (per axis: overflow-x-*, overflow-y-*) scrolls with the wheel, the touchpad (with a kinetic fling when the fingers lift), touch, the keyboard (arrows, Page Up/Down, Home/End, Space) and its scrollbars; overflow-hidden scrolls only from code. The window itself scrolls like a browser page when the content is taller or wider than it (App.RootClass "overflow-hidden" turns that off); h-screen and h-full on the outer element still mean the window's height. Wheel scrolling chains to the scrolling ancestors at an edge unless overscroll-contain stops it. Scrollbars are thin overlay bars that appear while scrolling; App.ClassicScrollbars (or the class scrollbar-classic) draws always-visible classic bars that take space. User scrolling animates; App.InstantScroll (or scroll-instant on an element) turns animation and flings off, scroll-smooth turns them back on, and the system's reduced-motion setting always does. Code scrolls with ElementRef's ScrollTo, ScrollBy and ScrollIntoView (animated with scroll-smooth, like CSS), and h.OnScroll and h.OnWheel observe it:

list := tela.UseElementRef()
_, y := tela.UseScroll(list)
header := Div(Class("h-16 bg-white shadow"), Style(tela.OpacityFn(func() float32 {
    return 1 - min(y.Get()/200, 1) // repaints only
})))
return Div(Class("flex h-screen flex-col"), header,
    Div(Ref(list), Class("grow overflow-y-auto"), rows),
    Button(OnClick(func(tela.MouseEvent) { list.ScrollTo(0, 0) }), Text("Top")))

Long lists use h.VirtualList, which builds only the rows in view, keeps the scroll position stable while rows of unknown height are measured, and scrolls to a row with ElementRef's ScrollToIndex and a ScrollAlign.

Text can be deferred the same way with h.TextFn: a Value read inside it lays out only the paragraph that holds the text, without building the component (a clock, a time label next to a seek bar).

A Value moved from a handler or a goroutine repaints only the element whose closure reads it:

pos := tela.UseValue(0.0)
Div(Class("size-8 rounded-full bg-indigo-500"),
    Style(tela.TranslateFn(func() (x, y float32) { return float32(pos.Get()), 0 })))
// later, from anywhere: pos.Set(120)

Hooks follow React's rules: call them at the top level of a component function, in the same order on every build, never inside conditions or loops. A hook called outside a component build panics with an explanation; in dev mode (TELA_DEV=1) a change of the hook order panics with both call sites. Everything runs on the UI thread except the Setter, Dispatcher and Value methods and Post, which are safe from any goroutine.

Text fields and forms

h.Input and h.Textarea are editable text fields with the editing, selection, clipboard, undo and IME behavior of a browser's; h.Form submits on Enter or a submit button (h.OnSubmit), and h.Label names and focuses its field. Their events are ChangeEvent (every edit, React's onChange), InputEvent (before an edit, cancelable), CompositionEvent, SelectEvent and SubmitEvent; ElementRef reads and selects their text (Value, Select, SetSelectionRange, SelectionRange). See fields.go for an example.

Snapshots and inspection

A package that defines `func Preview() tela.Node`, or a Previews variable, can be rendered headlessly with `tela snapshot`, and `tela inspect` prints its semantics tree as a Playwright aria snapshot (roles, names and states set by the h.Role and h.Aria... props or implied by the elements) or, with --layout, its layout tree. See the documentation of cmd/tela.

Stability

Node, Child, Prop, StyleProp, Color, Shadow, Gradient, GradientStop, Setter, Dispatcher, Callback, Ref, Context, Value, ElementRef, Rect, ScrollAlign, MouseEvent, PointerEvent, DragEvent, KeyboardEvent, FocusEvent, WheelEvent, ScrollEvent, InputEvent, ChangeEvent, CompositionEvent, SelectEvent, SubmitEvent, Key, Code, KeyLocation, MouseButton, MouseButtons and PointerKind are aliases of types defined in Tela's internal packages (the packages cannot be imported, but their exported fields and methods are part of the API shown here). Construct them with the functions of this package rather than with composite literals where one exists.

Index

Examples

Constants

View Source
const (
	// MouseButtonNone is no button, as in a move or enter event.
	MouseButtonNone = core.MouseButtonNone
	// MouseButtonLeft is the primary button: the left button (the right
	// one on a left-handed setup), a touch contact or a pen tip.
	MouseButtonLeft = core.MouseButtonLeft
	// MouseButtonRight is the secondary button.
	MouseButtonRight = core.MouseButtonRight
	// MouseButtonMiddle is the wheel button.
	MouseButtonMiddle = core.MouseButtonMiddle
	// MouseButtonBack is the "back" side button.
	MouseButtonBack = core.MouseButtonBack
	// MouseButtonForward is the "forward" side button.
	MouseButtonForward = core.MouseButtonForward
)

The mouse buttons.

View Source
const (
	PointerMouse = core.PointerMouse // a mouse or touchpad cursor
	PointerTouch = core.PointerTouch // a finger on a touchscreen
	PointerPen   = core.PointerPen   // a stylus
)

The pointer kinds.

View Source
const (
	// KeyLocationStandard is every key not listed below.
	KeyLocationStandard = core.KeyLocationStandard
	// KeyLocationLeft is the left Shift, Control, Alt or Meta key.
	KeyLocationLeft = core.KeyLocationLeft
	// KeyLocationRight is the right Shift, Control, Alt (AltGr) or Meta key.
	KeyLocationRight = core.KeyLocationRight
	// KeyLocationNumpad is a key of the numeric keypad.
	KeyLocationNumpad = core.KeyLocationNumpad
)

The key locations (the DOM's KeyboardEvent.location).

View Source
const (
	// AlignAuto scrolls as little as possible: not at all when the item
	// is fully visible, else to align its bottom (when it reaches past
	// the bottom of the viewport) or its top.
	AlignAuto = core.AlignAuto
	// AlignStart puts the item's top at the top of the viewport.
	AlignStart = core.AlignStart
	// AlignCenter centers the item in the viewport.
	AlignCenter = core.AlignCenter
	// AlignEnd puts the item's bottom at the bottom of the viewport.
	AlignEnd = core.AlignEnd
)

The alignments of ElementRef.ScrollToIndex. The first and last items include the list's padding: AlignStart on item 0 scrolls to the very top, AlignEnd on the last item to the very bottom.

Variables

View Source
var (
	// Transparent is fully transparent black.
	Transparent = gfx.Transparent
	// Black is opaque black.
	Black = gfx.Black
	// White is opaque white.
	White = gfx.White
)

Common colors.

Functions

func Post

func Post(fn func())

Post runs fn on the UI thread of the running application, in order with the state updates queued before and after it. It is safe from any goroutine; use it to touch UI-thread state (a Ref, a Callback's forwarder) from background work.

go func() {
    data := fetch()
    tela.Post(func() { cache.Current = data })
}()

Functions posted before Run starts run once its window is up. Headless trees (tests, snapshots) do not run posted functions unless they receive them (telatest.Mount does); until then they wait, in order.

func Run

func Run(app App) error

Run opens a window, shows app.Root and runs the event loop until the window is closed. It must be called from the main goroutine, normally from main. The window is drawn by Tela's own renderer on the native backend: Wayland or X11 on Linux, Win32 and Cocoa elsewhere.

Run returns an error when the platform cannot open a window (for example, no display is available). It panics when app.Root is nil.

func UseContext

func UseContext[T any](ctx *Context[T]) T

UseContext returns the value of the nearest Provider of ctx above the component, or the context's default when there is none.

theme := tela.UseContext(Theme)

func UseEffect

func UseEffect(effect func() func(), deps ...any)

UseEffect runs effect after the frame is presented, when its dependencies changed since the previous build. effect may return a cleanup function (or nil), which runs before the effect runs again and when the component unmounts.

The dependencies follow React:

tela.UseEffect(f)          // after every build
tela.UseEffect(f, nil)     // only after the first build (React's [])
tela.UseEffect(f, id, n)   // when id or n changed (compared with ==)

A single nil is a constant dependency, so the effect runs only on mount and its cleanup only on unmount: the idiom for subscriptions and timers.

tela.UseEffect(func() func() {
    tick := time.NewTicker(time.Second)
    done := make(chan struct{})
    go func() {
        for {
            select {
            case now := <-tick.C:
                setNow.Set(now) // safe from any goroutine
            case <-done:
                return
            }
        }
    }()
    return func() { tick.Stop(); close(done) }
}, nil)

Values that cannot be compared (functions, slices, maps) count as changed on every build; dev mode (TELA_DEV=1) reports them, and a dependency count that changes between builds. Pass an ID, a comparable value or a Callback instead.

Within a frame, the effects of children run before their parents', and every cleanup runs before the effects that follow it. Effects of a component whose build was skipped do not run. State set by an effect is applied in a later frame.

func UseId

func UseId() string

UseId returns a string that identifies this hook slot, for accessibility relations between elements (a label and its input). It is the same on every build, unique within the process, and the same for the same mount order (snapshots are deterministic).

id := tela.UseId()

func UseLayoutEffect

func UseLayoutEffect(effect func() func(), deps ...any)

UseLayoutEffect is UseEffect, but the effect runs in the frame after layout and before paint, with the same dependencies and cleanups. Its state updates are applied in the next frame.

tela.UseLayoutEffect(func() func() {
    log.Println("laid out")
    return nil
}, nil)

func UseMemo

func UseMemo[T any](compute func() T, deps ...any) T

UseMemo returns compute(), computed on the first build and again only when the dependencies changed (compared like UseEffect's: none recomputes on every build, a single nil never).

visible := tela.UseMemo(func() []Todo { return filter(todos, query) }, len(todos), query)

func UseStateSet

func UseStateSet[T any](initial T) (T, func(T))

UseStateSet is UseState for those who only set whole values: it returns the Setter's Set method as a plain function, made once per slot.

name, setName := tela.UseStateSet("")
setName("Ada")

func UseStateUpdate

func UseStateUpdate[T any](initial T) (T, func(func(T) T))

UseStateUpdate is UseState for those who only update from the previous state: it returns the Setter's Update method as a plain function, made once per slot.

count, update := tela.UseStateUpdate(0)
update(func(n int) int { return n + 1 })

Types

type App

type App struct {
	// Title is the window title.
	Title string
	// AppID identifies the application to the desktop environment (the
	// Wayland app_id, the X11 WM_CLASS). Use a reverse-DNS name such as
	// "com.example.notes". Empty lets the platform pick one.
	AppID string
	// Width and Height are the requested size of the window in logical
	// pixels. Zero means the platform's default size.
	Width, Height float32
	// RootClass are Tailwind classes for the root, which fills the
	// window. Use it to give the window a background ("bg-slate-900") or
	// a text style; without it the window is white and the text black.
	// The root is the viewport: content taller or wider than the window
	// scrolls, unless RootClass sets an overflow ("overflow-hidden" keeps
	// the window from scrolling; CSS's overflow propagation).
	RootClass string
	// Root is the root component. It is built as a component of its own,
	// so it may use hooks.
	Root func() Node
	// ClassicScrollbars draws always-visible classic scrollbars (a
	// Windows look) that take space next to the content, instead of the
	// default thin overlay bars that appear while scrolling. The classes
	// scrollbar-classic and scrollbar-overlay choose per element.
	ClassicScrollbars bool
	// InstantScroll makes wheel, keyboard and scrollbar scrolling jump
	// instead of animating, and touchpad and touch scrolling stop when
	// the fingers lift instead of flinging. Elements with scroll-smooth
	// still animate; the class scroll-instant does the same for one
	// element.
	InstantScroll bool
}

App describes an application window for Run.

tela.Run(tela.App{
    Title:             "Files",
    Root:              Root,
    ClassicScrollbars: true, // always-visible classic bars (a Windows look)
    InstantScroll:     true, // no scroll animation, no inertia
})

type Callback

type Callback[F any] = core.Callback[F]

Callback is a stable handle on the latest function given to UseCallback: cb.Fn() returns one forwarding function, the same on every build, that calls the latest function. It is comparable, so a props struct holding one stays comparable and CP can skip the component that receives it.

func UseCallback

func UseCallback[F any](fn F) Callback[F]

UseCallback stores fn as the latest function of this hook slot and returns the slot's Callback. cb.Fn() is one function of type F, the same on every build, that calls the latest fn when it is called (React's useEffectEvent): handlers keep a stable identity and still see the latest build's values. There are no dependencies. F must be a function type; UseCallback panics otherwise, and for a nil fn.

onSave := tela.UseCallback(func(e tela.MouseEvent) { save(draft) })
return tela.CP(Toolbar, ToolbarProps{OnSave: onSave}) // skips while draft changes

// in Toolbar:
Button(OnClick(p.OnSave.Fn()), Text("Save"))

Call the forwarder on the UI thread (handlers and effects run there); from other goroutines, use Post.

type ChangeEvent

type ChangeEvent = core.ChangeEvent

ChangeEvent is passed to h.OnChange after every edit of a text field's value, like React's onChange (the DOM's input event, not its change on blur): Value is the new value (with the text an IME is composing), InputType, Data and IsComposing describe the edit as in InputEvent. It bubbles, so a Form's OnChange sees the edits of all its fields.

name, setName := tela.UseState("")
Input(Value(name), OnChange(func(e tela.ChangeEvent) { setName.Set(e.Value) }))
// the same: Input(Bind(name, setName))

type Child

type Child = core.Child

Child is what an element takes: a Node (a child) or a Prop (h.Class, h.Style, h.Key, h.OnClick...). Props and children may be mixed in any order. The interface is sealed: passing a string or any other value is a compile error; wrap text in h.Text.

type Code

type Code = core.CodeValue

Code is a physical key: its position on the keyboard whatever the layout (a W3C KeyboardEvent code value), one of the Code constants. Use it for shortcuts that depend on the position (WASD), and Key for those that depend on the meaning (Ctrl+Z):

if e.Code == tela.CodeKeyW || e.Code == "ArrowUp" { moveUp() }
const (
	// CodeUnidentified is a key without a W3C code value.
	CodeUnidentified Code = "Unidentified"

	// Writing system keys (alphanumeric section). The comments give the
	// US layout label.
	CodeBackquote     Code = "Backquote"     // `~ (半角/全角 on Japanese keyboards)
	CodeBackslash     Code = "Backslash"     // \| (# on UK layouts, row C)
	CodeBracketLeft   Code = "BracketLeft"   // [{
	CodeBracketRight  Code = "BracketRight"  // ]}
	CodeComma         Code = "Comma"         // ,<
	CodeDigit0        Code = "Digit0"        // 0)
	CodeDigit1        Code = "Digit1"        // 1!
	CodeDigit2        Code = "Digit2"        // 2@
	CodeDigit3        Code = "Digit3"        // 3#
	CodeDigit4        Code = "Digit4"        // 4$
	CodeDigit5        Code = "Digit5"        // 5%
	CodeDigit6        Code = "Digit6"        // 6^
	CodeDigit7        Code = "Digit7"        // 7&
	CodeDigit8        Code = "Digit8"        // 8*
	CodeDigit9        Code = "Digit9"        // 9(
	CodeEqual         Code = "Equal"         // =+
	CodeIntlBackslash Code = "IntlBackslash" // between left Shift and Z on ISO keyboards
	CodeIntlRo        Code = "IntlRo"        // ろ, between / and right Shift (Japanese)
	CodeIntlYen       Code = "IntlYen"       // ¥, between = and Backspace (Japanese)
	CodeKeyA          Code = "KeyA"
	CodeKeyB          Code = "KeyB"
	CodeKeyC          Code = "KeyC"
	CodeKeyD          Code = "KeyD"
	CodeKeyE          Code = "KeyE"
	CodeKeyF          Code = "KeyF"
	CodeKeyG          Code = "KeyG"
	CodeKeyH          Code = "KeyH"
	CodeKeyI          Code = "KeyI"
	CodeKeyJ          Code = "KeyJ"
	CodeKeyK          Code = "KeyK"
	CodeKeyL          Code = "KeyL"
	CodeKeyM          Code = "KeyM"
	CodeKeyN          Code = "KeyN"
	CodeKeyO          Code = "KeyO"
	CodeKeyP          Code = "KeyP"
	CodeKeyQ          Code = "KeyQ"
	CodeKeyR          Code = "KeyR"
	CodeKeyS          Code = "KeyS"
	CodeKeyT          Code = "KeyT"
	CodeKeyU          Code = "KeyU"
	CodeKeyV          Code = "KeyV"
	CodeKeyW          Code = "KeyW"
	CodeKeyX          Code = "KeyX"
	CodeKeyY          Code = "KeyY"
	CodeKeyZ          Code = "KeyZ"
	CodeMinus         Code = "Minus"     // -_
	CodePeriod        Code = "Period"    // .>
	CodeQuote         Code = "Quote"     // '"
	CodeSemicolon     Code = "Semicolon" // ;:
	CodeSlash         Code = "Slash"     // /?

	// Functional keys of the alphanumeric section.
	CodeAltLeft      Code = "AltLeft"
	CodeAltRight     Code = "AltRight" // AltGr on many layouts
	CodeBackspace    Code = "Backspace"
	CodeCapsLock     Code = "CapsLock"
	CodeContextMenu  Code = "ContextMenu"
	CodeControlLeft  Code = "ControlLeft"
	CodeControlRight Code = "ControlRight"
	CodeEnter        Code = "Enter"
	CodeMetaLeft     Code = "MetaLeft"  // Windows, Command (⌘) or Super key
	CodeMetaRight    Code = "MetaRight" // Windows, Command (⌘) or Super key
	CodeShiftLeft    Code = "ShiftLeft"
	CodeShiftRight   Code = "ShiftRight"
	CodeSpace        Code = "Space"
	CodeTab          Code = "Tab"
	CodeConvert      Code = "Convert"    // 変換 (Japanese)
	CodeKanaMode     Code = "KanaMode"   // カタカナ/ひらがな/ローマ字 (Japanese)
	CodeLang1        Code = "Lang1"      // 한/영 (Korean), かな (Apple Japanese)
	CodeLang2        Code = "Lang2"      // 한자 (Korean), 英数 (Apple Japanese)
	CodeLang3        Code = "Lang3"      // カタカナ (Japanese)
	CodeLang4        Code = "Lang4"      // ひらがな (Japanese)
	CodeLang5        Code = "Lang5"      // 半角/全角 (Japanese)
	CodeNonConvert   Code = "NonConvert" // 無変換 (Japanese)

	// Control pad.
	CodeDelete   Code = "Delete"
	CodeEnd      Code = "End"
	CodeHelp     Code = "Help"
	CodeHome     Code = "Home"
	CodeInsert   Code = "Insert"
	CodePageDown Code = "PageDown"
	CodePageUp   Code = "PageUp"

	// Arrow pad.
	CodeArrowDown  Code = "ArrowDown"
	CodeArrowLeft  Code = "ArrowLeft"
	CodeArrowRight Code = "ArrowRight"
	CodeArrowUp    Code = "ArrowUp"

	// Numpad section.
	CodeNumLock              Code = "NumLock"
	CodeNumpad0              Code = "Numpad0"
	CodeNumpad1              Code = "Numpad1"
	CodeNumpad2              Code = "Numpad2"
	CodeNumpad3              Code = "Numpad3"
	CodeNumpad4              Code = "Numpad4"
	CodeNumpad5              Code = "Numpad5"
	CodeNumpad6              Code = "Numpad6"
	CodeNumpad7              Code = "Numpad7"
	CodeNumpad8              Code = "Numpad8"
	CodeNumpad9              Code = "Numpad9"
	CodeNumpadAdd            Code = "NumpadAdd"
	CodeNumpadBackspace      Code = "NumpadBackspace"
	CodeNumpadClear          Code = "NumpadClear"
	CodeNumpadClearEntry     Code = "NumpadClearEntry"
	CodeNumpadComma          Code = "NumpadComma"
	CodeNumpadDecimal        Code = "NumpadDecimal"
	CodeNumpadDivide         Code = "NumpadDivide"
	CodeNumpadEnter          Code = "NumpadEnter"
	CodeNumpadEqual          Code = "NumpadEqual"
	CodeNumpadHash           Code = "NumpadHash"
	CodeNumpadMemoryAdd      Code = "NumpadMemoryAdd"
	CodeNumpadMemoryClear    Code = "NumpadMemoryClear"
	CodeNumpadMemoryRecall   Code = "NumpadMemoryRecall"
	CodeNumpadMemoryStore    Code = "NumpadMemoryStore"
	CodeNumpadMemorySubtract Code = "NumpadMemorySubtract"
	CodeNumpadMultiply       Code = "NumpadMultiply"
	CodeNumpadParenLeft      Code = "NumpadParenLeft"
	CodeNumpadParenRight     Code = "NumpadParenRight"
	CodeNumpadStar           Code = "NumpadStar"
	CodeNumpadSubtract       Code = "NumpadSubtract"

	// Function section.
	CodeEscape      Code = "Escape"
	CodeF1          Code = "F1"
	CodeF2          Code = "F2"
	CodeF3          Code = "F3"
	CodeF4          Code = "F4"
	CodeF5          Code = "F5"
	CodeF6          Code = "F6"
	CodeF7          Code = "F7"
	CodeF8          Code = "F8"
	CodeF9          Code = "F9"
	CodeF10         Code = "F10"
	CodeF11         Code = "F11"
	CodeF12         Code = "F12"
	CodeF13         Code = "F13"
	CodeF14         Code = "F14"
	CodeF15         Code = "F15"
	CodeF16         Code = "F16"
	CodeF17         Code = "F17"
	CodeF18         Code = "F18"
	CodeF19         Code = "F19"
	CodeF20         Code = "F20"
	CodeF21         Code = "F21"
	CodeF22         Code = "F22"
	CodeF23         Code = "F23"
	CodeF24         Code = "F24"
	CodeFn          Code = "Fn"
	CodeFnLock      Code = "FnLock"
	CodePrintScreen Code = "PrintScreen"
	CodeScrollLock  Code = "ScrollLock"
	CodePause       Code = "Pause"

	// Media keys.
	CodeBrowserBack        Code = "BrowserBack"
	CodeBrowserFavorites   Code = "BrowserFavorites"
	CodeBrowserForward     Code = "BrowserForward"
	CodeBrowserHome        Code = "BrowserHome"
	CodeBrowserRefresh     Code = "BrowserRefresh"
	CodeBrowserSearch      Code = "BrowserSearch"
	CodeBrowserStop        Code = "BrowserStop"
	CodeEject              Code = "Eject"
	CodeLaunchApp1         Code = "LaunchApp1" // often "My Computer"
	CodeLaunchApp2         Code = "LaunchApp2" // often "Calculator"
	CodeLaunchMail         Code = "LaunchMail"
	CodeMediaPlayPause     Code = "MediaPlayPause"
	CodeMediaSelect        Code = "MediaSelect"
	CodeMediaStop          Code = "MediaStop"
	CodeMediaTrackNext     Code = "MediaTrackNext"
	CodeMediaTrackPrevious Code = "MediaTrackPrevious"
	CodePower              Code = "Power"
	CodeSleep              Code = "Sleep"
	CodeAudioVolumeDown    Code = "AudioVolumeDown"
	CodeAudioVolumeMute    Code = "AudioVolumeMute"
	CodeAudioVolumeUp      Code = "AudioVolumeUp"
	CodeWakeUp             Code = "WakeUp"

	// Legacy, non-standard and special keys.
	CodeHyper    Code = "Hyper"
	CodeSuper    Code = "Super"
	CodeTurbo    Code = "Turbo"
	CodeAbort    Code = "Abort"
	CodeResume   Code = "Resume"
	CodeSuspend  Code = "Suspend"
	CodeAgain    Code = "Again"
	CodeCopy     Code = "Copy"
	CodeCut      Code = "Cut"
	CodeFind     Code = "Find"
	CodeOpen     Code = "Open"
	CodePaste    Code = "Paste"
	CodeProps    Code = "Props"
	CodeSelect   Code = "Select"
	CodeUndo     Code = "Undo"
	CodeHiragana Code = "Hiragana"
	CodeKatakana Code = "Katakana"
)

Physical key codes: the values of KeyboardEvent.Code (W3C UI Events KeyboardEvent code values, https://www.w3.org/TR/uievents-code/), the position of the key whatever the layout: CodeKeyA is the key labeled A on a US keyboard (it types "q" on AZERTY). Code is a string type: e.Code == "KeyS" and e.Code == tela.CodeKeyS mean the same.

type Color

type Color = gfx.Color

Color is a non-premultiplied color with float32 components R, G, B and A. A (opacity) is in [0, 1]. R, G and B are extended sRGB, like CSS Color 4: they are in [0, 1] for colors inside the sRGB gamut, while a color outside it, such as a third of the Tailwind palette once converted from OKLCH, keeps components below 0 or above 1 and is clipped when painted, as browsers do. Clamp before converting to 8-bit values. The zero value is transparent black.

Color is an alias of an internal type: use its fields and methods (WithAlpha, MulAlpha, String...) and the constructors Hex, RGB, RGBA and OKLCH.

func Hex

func Hex(s string) Color

Hex parses a CSS hex color: "#rgb", "#rgba", "#rrggbb" or "#rrggbbaa". It panics with the reason when s is not valid, since a literal that does not parse is a programmer error.

tela.Hex("#1e1e2e")
Example
package main

import (
	"fmt"

	"github.com/kuronosu/tela"
)

func main() {
	c := tela.Hex("#1e1e2e")
	fmt.Println(c.R == tela.RGB(30, 30, 46).R, c)
}
Output:
true #1e1e2e

func OKLCH

func OKLCH(l, c, h float32) Color

OKLCH returns the opaque color with OKLCH lightness l (0 to 1), chroma c (about 0 to 0.4) and hue h in degrees, the notation of the Tailwind v4 palette: tela.OKLCH(0.637, 0.237, 25.331) is red-500. Use WithAlpha for transparency. The result is not gamut-mapped (see Color).

func RGB

func RGB(r, g, b uint8) Color

RGB returns the opaque color with the given 8-bit components.

func RGBA

func RGBA(r, g, b uint8, a float32) Color

RGBA returns the color with the given 8-bit components and an alpha from 0 (transparent) to 1 (opaque), like CSS rgba(r, g, b, a).

type CompositionEvent

type CompositionEvent = core.CompositionEvent

CompositionEvent is passed to h.OnCompositionStart, h.OnCompositionUpdate and h.OnCompositionEnd while an input method (IME: Japanese, Chinese, Korean...) composes text in a text field: Data is the text being composed. It bubbles.

type Context

type Context[T any] = core.Context[T]

Context passes a value down the tree without props: see CreateContext.

func CreateContext

func CreateContext[T any](def T) *Context[T]

CreateContext returns a new context whose UseContext outside any Provider returns def. Create contexts once, at package level.

var Theme = tela.CreateContext("light")

func App() tela.Node {
    theme, _ := tela.UseState("dark")
    return Theme.Provider(theme, tela.C(Page))
}

ctx.Provider(value, children...) returns a node without a box that gives value to its children's subtrees (only h.Key is accepted as a prop). When the Provider gets a value that is not == to the previous one (values that cannot be compared always count as changed), only the components that read it with UseContext build again, even when the components between them skip their build.

type Dispatcher

type Dispatcher[A any] = core.Dispatcher[A]

Dispatcher sends actions to one UseReducer slot: d.Dispatch(action). Like Setter, it is comparable, stable for its slot and safe from any goroutine.

func UseReducer

func UseReducer[S, A any](reducer func(S, A) S, initial S) (S, Dispatcher[A])

UseReducer returns the state of this hook slot and a Dispatcher whose actions update it with reducer (React's useReducer). The first build stores initial. Queued actions apply in order on the UI thread with the reducer of the latest build, each receiving the state left by the previous one; reducer must be pure. For comparable states, a result == to the current state does not rebuild.

type action struct{ kind string }

func reduce(n int, a action) int {
    switch a.kind {
    case "inc":
        return n + 1
    case "reset":
        return 0
    }
    return n
}

count, d := tela.UseReducer(reduce, 0)
d.Dispatch(action{"inc"})

type DragEvent

type DragEvent = core.DragEvent

DragEvent is passed to h.OnDragStart, h.OnDrag and h.OnDragEnd: the position, the movement since the previous drag event (DeltaX, DeltaY) and since the press (OffsetX, OffsetY), and the velocity in pixels per second (VelocityX, VelocityY; meaningful at the end).

type ElementRef

type ElementRef = core.ElementRef

ElementRef is a handle on the element it is attached to with h.Ref, returned by UseElementRef: the same comparable value on every build. Its methods act on the element while it is mounted and do nothing (or report false) otherwise; call them from event handlers and effects.

  • Focus focuses the element, if it is focusable (a Button, an h.Input or h.Textarea, or an element with h.TabIndex, not disabled or hidden).
  • Blur removes the focus from the element if it has it.
  • Focused reports whether the element has the focus of its window.
  • ScrollTo(x, y) and ScrollBy(dx, dy) scroll a scrolling element (overflow auto, scroll or hidden) to or by offsets, clamped to its content; ScrollOffset returns its offsets (the DOM's scrollLeft and scrollTop). They jump, or animate when the element has scroll-smooth, and do nothing (or return 0) for other elements.
  • ScrollIntoView scrolls every scrolling ancestor of the element so that it is visible, each as little as possible (block and inline "nearest").
  • Bounds returns the element's border box in window coordinates as a Rect, moved by scrolling and transforms (getBoundingClientRect); the zero Rect before it is laid out. UseBounds follows it in a Value.
  • ScrollToIndex(i, align) scrolls an h.VirtualList to item i, placed as ScrollAlign says, and keeps it there while the items around it are measured; it does nothing for other elements.
  • Text fields (h.Input, h.Textarea): Value returns the value (the DOM's input.value: mainly for uncontrolled fields), Select selects all of it, SetSelectionRange(start, end) selects part of it and SelectionRange returns the selection, in UTF-8 byte offsets of the value (HTML's select(), setSelectionRange, selectionStart and selectionEnd); on other elements they do nothing and return "" or 0, 0.

For example, "/" focuses a search box:

search := tela.UseElementRef()
return Div(OnKeyDown(func(e tela.KeyboardEvent) {
        if e.Key == "/" && !search.Focused() {
            e.PreventDefault()
            search.Focus()
            search.Select()
        }
    }),
    Input(Ref(search), Type("search"), Placeholder("Search"), Class("rounded-md border px-2 py-1")),
)

A "back to top" button for a list:

list := tela.UseElementRef()
return Div(Class("relative"),
    Div(Ref(list), Class("h-96 overflow-y-auto scroll-smooth"), rows),
    Button(OnClick(func(tela.MouseEvent) { list.ScrollTo(0, 0) }), Text("Top")))

func UseElementRef

func UseElementRef() ElementRef

UseElementRef returns the ElementRef of this hook slot, the same on every build. Attach it to an element with h.Ref, then act on the element from handlers and effects (React's useRef with a DOM element):

func Search() tela.Node {
    input := tela.UseElementRef()
    tela.UseEffect(func() func() {
        input.Focus()
        return nil
    }, nil)
    return Div(Ref(input), TabIndex(0), Class("rounded-md p-2 focus:ring-2"), Text("Search"))
}

type FocusEvent

type FocusEvent = core.FocusEvent

FocusEvent is passed to h.OnFocus and h.OnBlur. Like React's onFocus and onBlur, focus events bubble: a container's OnFocus also runs when one of its descendants gains the focus.

Div(OnFocus(func(tela.FocusEvent) { setEditing.Set(true) }),
    OnBlur(func(tela.FocusEvent) { setEditing.Set(false) }),
    ...)

type Gradient

type Gradient = style.Gradient

Gradient is a background gradient; build one with LinearGradient.

func LinearGradient

func LinearGradient(angleDeg float32, stops ...GradientStop) Gradient

LinearGradient returns a linear gradient in the direction of angleDeg: 0 points up, 90 to the right and 180 (CSS's default) down. Colors interpolate in Oklab, like Tailwind v4.

type GradientStop

type GradientStop = style.ColorStop

GradientStop is a color stop of a gradient; build one with Stop or StopAt.

func Stop

func Stop(c Color) GradientStop

Stop returns a gradient stop whose position is spread automatically between its neighbors, as in CSS.

func StopAt

func StopAt(c Color, percent float32) GradientStop

StopAt returns a gradient stop at a percentage (0 to 100) of the gradient line.

type InputEvent

type InputEvent = core.InputEvent

InputEvent is passed to h.OnBeforeInput before a text field's value changes by an edit of the user (the DOM's beforeinput): InputType names the edit with the W3C Input Events names ("insertText", "insertFromPaste", "insertLineBreak", "deleteContentBackward", "deleteWordBackward", "deleteByCut", "historyUndo", "insertCompositionText"...), Data is the inserted text (empty for deletions), IsComposing reports an IME composition. PreventDefault cancels the edit, except during a composition and for the text an IME deletes around the caret. It bubbles.

// Digits only.
Input(OnBeforeInput(func(e tela.InputEvent) {
    if strings.Trim(e.Data, "0123456789") != "" {
        e.PreventDefault()
    }
}))

type Key

type Key = core.KeyValue

Key is a logical key: what the key means with the active layout and modifiers (a W3C KeyboardEvent key value). It is a named key, one of the Key constants ("Enter", "Escape", "ArrowLeft", "Shift"), or the character the key types ("a", "A", "é", " " for the space bar). Ctrl does not change it: Ctrl+S has Key "s". Compare it with a constant or a string literal:

if e.Key == tela.KeyEnter || e.Key == "+" { ... }
const (
	// KeyUnidentified is a key with neither a name nor a character.
	KeyUnidentified Key = "Unidentified"

	// Modifier keys. AltGraph is AltGr (ISO_Level3_Shift); Meta is the
	// Windows, Command (⌘) or Super key, as browsers report it on every
	// OS.
	KeyAlt        Key = "Alt"
	KeyAltGraph   Key = "AltGraph"
	KeyCapsLock   Key = "CapsLock"
	KeyControl    Key = "Control"
	KeyFn         Key = "Fn"
	KeyFnLock     Key = "FnLock"
	KeyMeta       Key = "Meta"
	KeyNumLock    Key = "NumLock"
	KeyScrollLock Key = "ScrollLock"
	KeyShift      Key = "Shift"
	KeySymbol     Key = "Symbol"
	KeySymbolLock Key = "SymbolLock"
	KeyHyper      Key = "Hyper"
	KeySuper      Key = "Super"

	// Whitespace keys (the space bar is the character " ").
	KeyEnter Key = "Enter"
	KeyTab   Key = "Tab"

	// Navigation keys.
	KeyArrowDown  Key = "ArrowDown"
	KeyArrowLeft  Key = "ArrowLeft"
	KeyArrowRight Key = "ArrowRight"
	KeyArrowUp    Key = "ArrowUp"
	KeyEnd        Key = "End"
	KeyHome       Key = "Home"
	KeyPageDown   Key = "PageDown"
	KeyPageUp     Key = "PageUp"

	// Editing keys.
	KeyBackspace Key = "Backspace"
	KeyClear     Key = "Clear"
	KeyCopy      Key = "Copy"
	KeyCrSel     Key = "CrSel"
	KeyCut       Key = "Cut"
	KeyDelete    Key = "Delete"
	KeyEraseEof  Key = "EraseEof"
	KeyExSel     Key = "ExSel"
	KeyInsert    Key = "Insert"
	KeyPaste     Key = "Paste"
	KeyRedo      Key = "Redo"
	KeyUndo      Key = "Undo"

	// UI keys.
	KeyAccept      Key = "Accept"
	KeyAgain       Key = "Again"
	KeyAttn        Key = "Attn"
	KeyCancel      Key = "Cancel"
	KeyContextMenu Key = "ContextMenu"
	KeyEscape      Key = "Escape"
	KeyExecute     Key = "Execute"
	KeyFind        Key = "Find"
	KeyHelp        Key = "Help"
	KeyPause       Key = "Pause"
	KeyPlay        Key = "Play"
	KeyProps       Key = "Props"
	KeySelect      Key = "Select"
	KeyZoomIn      Key = "ZoomIn"
	KeyZoomOut     Key = "ZoomOut"

	// Device keys.
	KeyBrightnessDown Key = "BrightnessDown"
	KeyBrightnessUp   Key = "BrightnessUp"
	KeyEject          Key = "Eject"
	KeyLogOff         Key = "LogOff"
	KeyPower          Key = "Power"
	KeyPowerOff       Key = "PowerOff"
	KeyPrintScreen    Key = "PrintScreen"
	KeyHibernate      Key = "Hibernate"
	KeyStandby        Key = "Standby"
	KeyWakeUp         Key = "WakeUp"

	// IME and composition keys. Dead is a dead key (a pending accent or
	// other combining mark; the next key types the result).
	KeyAllCandidates     Key = "AllCandidates"
	KeyAlphanumeric      Key = "Alphanumeric"
	KeyCodeInput         Key = "CodeInput"
	KeyCompose           Key = "Compose"
	KeyConvert           Key = "Convert"
	KeyDead              Key = "Dead"
	KeyFinalMode         Key = "FinalMode"
	KeyGroupFirst        Key = "GroupFirst"
	KeyGroupLast         Key = "GroupLast"
	KeyGroupNext         Key = "GroupNext"
	KeyGroupPrevious     Key = "GroupPrevious"
	KeyModeChange        Key = "ModeChange"
	KeyNextCandidate     Key = "NextCandidate"
	KeyNonConvert        Key = "NonConvert"
	KeyPreviousCandidate Key = "PreviousCandidate"
	KeyProcess           Key = "Process"
	KeySingleCandidate   Key = "SingleCandidate"
	KeyHangulMode        Key = "HangulMode"
	KeyHanjaMode         Key = "HanjaMode"
	KeyJunjaMode         Key = "JunjaMode"
	KeyEisu              Key = "Eisu"
	KeyHankaku           Key = "Hankaku"
	KeyHiragana          Key = "Hiragana"
	KeyHiraganaKatakana  Key = "HiraganaKatakana"
	KeyKanaMode          Key = "KanaMode"
	KeyKanjiMode         Key = "KanjiMode"
	KeyKatakana          Key = "Katakana"
	KeyRomaji            Key = "Romaji"
	KeyZenkaku           Key = "Zenkaku"
	KeyZenkakuHankaku    Key = "ZenkakuHankaku"

	// General-purpose function keys.
	KeyF1  Key = "F1"
	KeyF2  Key = "F2"
	KeyF3  Key = "F3"
	KeyF4  Key = "F4"
	KeyF5  Key = "F5"
	KeyF6  Key = "F6"
	KeyF7  Key = "F7"
	KeyF8  Key = "F8"
	KeyF9  Key = "F9"
	KeyF10 Key = "F10"
	KeyF11 Key = "F11"
	KeyF12 Key = "F12"
	KeyF13 Key = "F13"
	KeyF14 Key = "F14"
	KeyF15 Key = "F15"
	KeyF16 Key = "F16"
	KeyF17 Key = "F17"
	KeyF18 Key = "F18"
	KeyF19 Key = "F19"
	KeyF20 Key = "F20"
	KeyF21 Key = "F21"
	KeyF22 Key = "F22"
	KeyF23 Key = "F23"
	KeyF24 Key = "F24"

	// Multimedia keys.
	KeyClose              Key = "Close"
	KeyMailForward        Key = "MailForward"
	KeyMailReply          Key = "MailReply"
	KeyMailSend           Key = "MailSend"
	KeyMediaClose         Key = "MediaClose"
	KeyMediaFastForward   Key = "MediaFastForward"
	KeyMediaPause         Key = "MediaPause"
	KeyMediaPlay          Key = "MediaPlay"
	KeyMediaPlayPause     Key = "MediaPlayPause"
	KeyMediaRecord        Key = "MediaRecord"
	KeyMediaRewind        Key = "MediaRewind"
	KeyMediaStop          Key = "MediaStop"
	KeyMediaTrackNext     Key = "MediaTrackNext"
	KeyMediaTrackPrevious Key = "MediaTrackPrevious"
	KeyNew                Key = "New"
	KeyOpen               Key = "Open"
	KeyPrint              Key = "Print"
	KeySave               Key = "Save"
	KeySpellCheck         Key = "SpellCheck"

	// Audio keys.
	KeyAudioVolumeDown      Key = "AudioVolumeDown"
	KeyAudioVolumeUp        Key = "AudioVolumeUp"
	KeyAudioVolumeMute      Key = "AudioVolumeMute"
	KeyMicrophoneVolumeMute Key = "MicrophoneVolumeMute"

	// Application launch keys.
	KeyLaunchApplication1  Key = "LaunchApplication1"
	KeyLaunchApplication2  Key = "LaunchApplication2"
	KeyLaunchCalendar      Key = "LaunchCalendar"
	KeyLaunchMail          Key = "LaunchMail"
	KeyLaunchMediaPlayer   Key = "LaunchMediaPlayer"
	KeyLaunchMusicPlayer   Key = "LaunchMusicPlayer"
	KeyLaunchPhone         Key = "LaunchPhone"
	KeyLaunchScreenSaver   Key = "LaunchScreenSaver"
	KeyLaunchSpreadsheet   Key = "LaunchSpreadsheet"
	KeyLaunchWebBrowser    Key = "LaunchWebBrowser"
	KeyLaunchWebCam        Key = "LaunchWebCam"
	KeyLaunchWordProcessor Key = "LaunchWordProcessor"

	// Browser keys.
	KeyBrowserBack      Key = "BrowserBack"
	KeyBrowserFavorites Key = "BrowserFavorites"
	KeyBrowserForward   Key = "BrowserForward"
	KeyBrowserHome      Key = "BrowserHome"
	KeyBrowserRefresh   Key = "BrowserRefresh"
	KeyBrowserSearch    Key = "BrowserSearch"
	KeyBrowserStop      Key = "BrowserStop"
)

Named keys: the values of KeyboardEvent.Key that are not characters (W3C UI Events KeyboardEvent key values, https://www.w3.org/TR/uievents-key/). A key that produces a character has that character as its Key ("a", "A", "1", "é", and " " for the space bar), so compare with a string literal: e.Key == "z". Key is a string type: e.Key == "Enter" and e.Key == tela.KeyEnter mean the same.

type KeyLocation

type KeyLocation = core.KeyLocation

KeyLocation tells apart keys with the same meaning: the left and right Shift, Control, Alt and Meta keys, and the numeric keypad.

type KeyboardEvent

type KeyboardEvent = core.KeyboardEvent

KeyboardEvent is passed to h.OnKeyDown and h.OnKeyUp: the logical Key, the physical Code, its Location, Repeat for a keydown generated while the key is held, and the modifiers held after the event (Shift, Ctrl, Alt, Meta; Meta is the Windows, Command or Super key). StopPropagation stops the bubbling; PreventDefault cancels the key's default action (Tab's focus move, the click of Enter or Space on a button, the text a key types into a text field). Typed text reaches text fields (h.Input, h.Textarea), which report it with h.OnChange.

OnKeyDown(func(e tela.KeyboardEvent) {
    switch {
    case e.Key == tela.KeyArrowDown:
        e.PreventDefault()
        setIndex.Update(func(i int) int { return min(i+1, n-1) })
    case e.Ctrl && e.Key == "a":
        selectAll()
    }
})

type MouseButton

type MouseButton = core.MouseButton

MouseButton is a mouse button, or the touch contact or pen tip that stands for the primary one.

type MouseButtons

type MouseButtons = core.MouseButtons

MouseButtons is a set of held buttons: e.Buttons.Has(tela.MouseButtonLeft).

type MouseEvent

type MouseEvent = core.MouseEvent

MouseEvent is passed to the mouse handlers (h.OnClick, h.OnMouseDown, h.OnContextMenu...). X and Y are window coordinates in logical pixels; LocalX and LocalY are relative to the border box of the element whose handler runs. ClickCount is 1 for a click, 2 for the second click of a double click. StopPropagation stops the bubbling; PreventDefault marks the event (on h.OnPointerDown it suppresses the press's mouse events, not the click).

type Node

type Node = core.Node

Node is an immutable, cheap description of a piece of UI: an element (h.Div), a text (h.Text), a component or a fragment. Function components return one, and every build creates new ones; the persistent state lives elsewhere, keyed by position and key (ARCHITECTURE section 6).

The zero Node renders nothing. As a child of an element it keeps the positions of its siblings, so h.If(cond, node) does not remount the siblings when cond changes.

func C

func C(fn func() Node) Node

C returns a node that builds fn as a component with its own state: a component boundary (ARCHITECTURE section 7). Calling fn() directly instead runs it inside the caller's build, as a custom hook whose hooks belong to the caller.

Div(tela.C(Header), tela.C(Body))

A C component builds whenever its parent builds, since fn may capture values that changed; use CP for automatic skipping. Only the very same node (one kept in a variable or a UseMemo) is not built again.

func CP

func CP[P comparable](fn func(P) Node, props P) Node

CP returns a node that builds fn(props) as a component with its own state. When its parent builds again with props == to the previous ones, and neither its state nor a context it reads changed, the build is skipped (Jetpack Compose's automatic skipping): free memoization.

type CardProps struct {
    Title string
    OnOpen tela.Callback[func(tela.MouseEvent)]
}

func Card(p CardProps) tela.Node { ... }

tela.CP(Card, CardProps{Title: "Inbox", OnOpen: onOpen})

Give CP a named function and pass everything it uses in its props: variables captured by a closure are not compared, so dev mode (TELA_DEV=1) reports anonymous functions once per call site. Keep props comparable by passing Callback values instead of funcs, and IDs instead of slices or maps; a props value whose == panics counts as changed.

func Memo

func Memo[P any](fn func(P) Node, props P, equal func(a, b P) bool) Node

Memo is CP for props that are not comparable, or that compare in another way: the build is skipped when equal(old, new) reports them equal. equal must be pure.

tela.Memo(Chart, ChartProps{Points: pts}, func(a, b ChartProps) bool {
    return slices.Equal(a.Points, b.Points)
})

func WithKey

func WithKey(n Node, k any) Node

WithKey returns a copy of n with the key k, whatever its kind. Use it to key a node that cannot take the h.Key prop, such as a component or a fragment. k must be a non-nil comparable value, such as an ID; otherwise WithKey panics.

type PointerEvent

type PointerEvent = core.PointerEvent

PointerEvent is passed to the pointer handlers (h.OnPointerDown..., h.OnLongPress): the mouse fields plus the pointer's Kind, PointerID, Pressure and IsPrimary.

type PointerKind

type PointerKind = core.PointerKind

PointerKind is the device behind a pointer.

type Previews

type Previews map[string]func() Node

Previews are the named screens of a package, for `tela snapshot` and `tela inspect` (ARCHITECTURE section 17). Define one as a package-level variable called Previews:

var Previews = tela.Previews{
    "Login":    LoginScreen,
    "Settings": SettingsScreen,
}

Each function builds a screen as a component, so it may use hooks. The CLI renders the preview selected with --preview NAME; without the flag it renders the first name in byte order (see Previews.Names), so a name such as "1 Login" can pick the default. A package with a single screen can define `func Preview() tela.Node` instead.

Example
package main

import (
	"fmt"

	"github.com/kuronosu/tela"
	"github.com/kuronosu/tela/internal/core"
)

func main() {
	login := func() tela.Node { return core.Text("Login") }
	settings := func() tela.Node { return core.Text("Settings") }

	previews := tela.Previews{"Settings": settings, "Login": login}
	// `tela snapshot` renders the first name unless --preview says otherwise.
	fmt.Println(previews.Names())
}
Output:
[Login Settings]

func (Previews) Names

func (p Previews) Names() []string

Names returns the preview names in the order the CLI uses: sorted by bytes. The first is the default preview.

type Prop

type Prop = core.Prop

Prop is a property of an element: classes, typed style, key, event handlers, an image source. Built by the functions of package h.

type Rect

type Rect = core.Rect

Rect is a rectangle in logical pixels: the position of its top-left corner (X, Y) and its size (Width, Height). Right, Bottom and Contains derive from them. It is what ElementRef.Bounds and UseBounds return, in window coordinates (the window's top-left corner is 0, 0), like the DOM's DOMRect.

type Ref

type Ref[T any] = core.Ref[T]

Ref is a mutable box returned by UseRef: the same pointer on every build. Changing Current does not rebuild.

func UseRef

func UseRef[T any](initial T) *Ref[T]

UseRef returns the Ref of this hook slot, a box whose Current field persists across builds; the first build sets it to initial. Changing Current does not rebuild: use it for values that rendering does not show, such as a timer or the previous value of a prop.

renders := tela.UseRef(0)
renders.Current++

type ScrollAlign

type ScrollAlign = core.ScrollAlign

ScrollAlign is where ElementRef.ScrollToIndex puts an item of an h.VirtualList in its viewport (TanStack Virtual's align).

type ScrollEvent

type ScrollEvent = core.ScrollEvent

ScrollEvent is passed to h.OnScroll after an element's scroll offsets changed, by the user or by code (the DOM's scroll event: at most once per frame, on the scrolling element only; it does not bubble). The fields are the DOM's scroll properties in logical pixels: ScrollLeft and ScrollTop (the offsets), ScrollWidth and ScrollHeight (the content size), ClientWidth and ClientHeight (the visible size).

Div(Class("h-96 overflow-y-auto"), OnScroll(func(e tela.ScrollEvent) {
    if e.ScrollTop+e.ClientHeight >= e.ScrollHeight-200 {
        loadMore()
    }
}), rows)

type SelectEvent

type SelectEvent = core.SelectEvent

SelectEvent is passed to h.OnSelect when the user selects text in a text field or code changes its selection: Start and End are the selection in UTF-8 byte offsets of the value. It bubbles.

type Setter

type Setter[T any] = core.Setter[T]

Setter sets the state of one UseState slot. It is a small comparable value, the same on every build of the slot, so it can be stored, passed to goroutines and put in props compared by CP. Its methods are safe from any goroutine: they queue the update and wake the UI thread, which applies the queued updates in order before the next build. Updates to a component that has been unmounted are dropped. The zero Setter is not tied to any state; its methods panic.

setCount.Set(0)
setCount.Update(func(n int) int { return n + 1 })

Set(v) replaces the state; Update(f) computes it from the state left by the previous queued update, so two queued Update(inc) add 2. f runs on the UI thread and must be pure. For comparable types, a new state == to the current one does not rebuild.

func UseState

func UseState[T any](initial T) (T, Setter[T])

UseState returns the state of this hook slot and its Setter (ARCHITECTURE section 7). The first build stores initial; later builds return the latest value set. Setting a different value rebuilds the component in the next frame.

func Counter() tela.Node {
    count, setCount := tela.UseState(0)
    return Button(OnClick(func(tela.MouseEvent) { setCount.Set(count + 1) }),
        Textf("Clicked %d times", count))
}

Use setCount.Update(func(n int) int { return n + 1 }) when the new state depends on the old one and several updates may be queued before the next build (from goroutines, or several events in one frame).

Hooks follow React's rules: call them at the top level of a component function, in the same order on every build, never inside conditions or loops. UseState panics when called outside a component build or when the call order changed since the previous build.

type Shadow

type Shadow = style.Shadow

Shadow is one box shadow layer: OffsetX, OffsetY, Blur and Spread in logical pixels, its Color, and Inset for an inner shadow.

type StyleProp

type StyleProp = style.Op

StyleProp is one typed style declaration, built by the functions below and passed to h.Style. Typed props apply after the element's classes, so they win over a class that sets the same property; use them for values that come from code (a measured width, a color from state). Lengths are logical pixels.

func BackgroundGradient

func BackgroundGradient(g Gradient) StyleProp

BackgroundGradient sets a gradient as the background image, over the background color.

func Bg

func Bg(c Color) StyleProp

Bg sets the background color.

func BorderColor

func BorderColor(c Color) StyleProp

BorderColor sets the color of the four borders.

func BorderWidth

func BorderWidth(px float32) StyleProp

BorderWidth sets the width of the four borders in pixels.

func Bottom

func Bottom(px float32) StyleProp

Bottom sets the bottom inset of a positioned element in pixels.

func BoxShadow

func BoxShadow(layers ...Shadow) StyleProp

BoxShadow sets the box shadows, replacing those of shadow-* classes.

func FontSize

func FontSize(px float32) StyleProp

FontSize sets the font size in pixels.

func FontWeight

func FontWeight(w float32) StyleProp

FontWeight sets the font weight, from 100 to 900.

func Gap

func Gap(px float32) StyleProp

Gap sets the row and column gap of a flex container in pixels.

func Height

func Height(px float32) StyleProp

Height sets the height in pixels.

func HeightFn

func HeightFn(fn func() float32) StyleProp

HeightFn defers Height, in pixels, to the layout phase: a change of the Values fn reads lays the element out again, without building it.

func Left

func Left(px float32) StyleProp

Left sets the left inset of a positioned element in pixels.

func Margin

func Margin(px float32) StyleProp

Margin sets the margin of the four sides in pixels.

func MaxHeight

func MaxHeight(px float32) StyleProp

MaxHeight sets the maximum height in pixels.

func MaxWidth

func MaxWidth(px float32) StyleProp

MaxWidth sets the maximum width in pixels.

func MinHeight

func MinHeight(px float32) StyleProp

MinHeight sets the minimum height in pixels.

func MinWidth

func MinWidth(px float32) StyleProp

MinWidth sets the minimum width in pixels.

func Opacity

func Opacity(v float32) StyleProp

Opacity sets the opacity from 0 (transparent) to 1.

func OpacityFn

func OpacityFn(fn func() float32) StyleProp

OpacityFn defers Opacity to the paint phase: a change of the Values fn reads repaints the element, without layout.

func Padding

func Padding(px float32) StyleProp

Padding sets the padding of the four sides in pixels.

func PaddingXY

func PaddingXY(x, y float32) StyleProp

PaddingXY sets the horizontal and vertical padding in pixels.

func Radius

func Radius(px float32) StyleProp

Radius sets the radius of the four corners in pixels.

func Right(px float32) StyleProp

Right sets the right inset of a positioned element in pixels.

func Rotate

func Rotate(deg float32) StyleProp

Rotate rotates the element by deg degrees, clockwise.

func Scale

func Scale(x, y float32) StyleProp

Scale scales the element by x and y (1 is no change) without affecting layout.

func TextColor

func TextColor(c Color) StyleProp

TextColor sets the text color, which the element's descendants inherit.

func Top

func Top(px float32) StyleProp

Top sets the top inset of a positioned element in pixels.

func Translate

func Translate(x, y float32) StyleProp

Translate moves the element by x and y pixels without affecting layout.

func TranslateFn

func TranslateFn(fn func() (x, y float32)) StyleProp

TranslateFn defers Translate to the paint phase: a change of the Values fn reads repaints the element, without layout.

Style(tela.TranslateFn(func() (x, y float32) { return float32(pos.Get()), 0 }))

func Width

func Width(px float32) StyleProp

Width sets the width in pixels.

func WidthFn

func WidthFn(fn func() float32) StyleProp

WidthFn defers Width, in pixels, to the layout phase: a change of the Values fn reads lays the element out again, without building it.

func ZIndex

func ZIndex(z int) StyleProp

ZIndex sets an explicit z-index (it applies to positioned elements and flex items).

type SubmitEvent

type SubmitEvent = core.SubmitEvent

SubmitEvent is passed to h.OnSubmit when a h.Form is submitted: Enter in a single-line field of the form, or a click on a submit button inside it. Tela does not navigate; PreventDefault exists for familiarity.

Form(OnSubmit(func(e tela.SubmitEvent) {
    e.PreventDefault()
    login(email, password)
}), ...)

type Value

type Value[T any] = core.Value[T]

Value is an observable value held by one UseValue hook slot: v.Get() reads it, v.Set(x) and v.Update(f) change it. The *Value is the same pointer on every build, so it is comparable and can be passed in props compared by CP or through a context. Set and Update are safe from any goroutine: they queue the change, in order with the state updates, and the UI thread applies it before the next frame. Get is safe from any goroutine too and returns the value the UI thread applied last (a change still in the queue is not visible yet); Latest returns the value as it will be once the queued changes apply, which is what an event handler that has just called Set wants to read. Latest never subscribes anything.

What a change redoes depends on where Get was called; see UseValue. For comparable types, a value == to the current one changes nothing.

func UseBounds

func UseBounds(ref ElementRef) *Value[Rect]

UseBounds returns a Value that follows the bounds of the element ref is attached to with h.Ref: its border box in window coordinates, as ElementRef.Bounds returns it (moved by scrolling and transforms, the zero Rect while the element is not mounted or displayed). It is a ResizeObserver that also sees the position: use it to size or place one element after another, or to anchor a floating element.

The Value is updated at the end of every frame, when the bounds changed since the last one, so a component that reads it rebuilds, and a deferred style closure that reads it lays out or repaints, in the frame after the change. Frames where nothing moved cost one comparison and request no further frame. The same Value is returned on every build.

func Meter() tela.Node {
    track := tela.UseElementRef()
    b := tela.UseBounds(track)
    return Div(Ref(track), Class("h-2 rounded bg-slate-200"),
        Div(Class("h-2 rounded bg-indigo-500"),
            Style(tela.WidthFn(func() float32 { return b.Get().Width * 0.4 }))))
}

In an event handler, ElementRef.Bounds gives the same rectangle without a Value: a seek bar turns the pointer's x into a fraction with e.X-ref.Bounds().X over ref.Bounds().Width.

func UseScroll

func UseScroll(ref ElementRef) (x, y *Value[float32])

UseScroll returns two Value objects that follow the scroll offsets of the element ref is attached to (zero while it is not a mounted scrolling element): x is its horizontal offset (scrollLeft) and y its vertical one (scrollTop), in logical pixels. They change on the UI thread as the element scrolls, so a deferred style closure that reads them repaints or lays out only its element, without rebuilding (Motion's useScroll). The same Values are returned on every build.

func Page() tela.Node {
    list := tela.UseElementRef()
    _, y := tela.UseScroll(list)
    header := Div(Class("h-16 bg-white shadow"), Style(tela.OpacityFn(func() float32 {
        return 1 - min(y.Get()/200, 1) // fades out over the first 200 px
    })))
    return Div(Class("flex h-screen flex-col"), header,
        Div(Ref(list), Class("grow overflow-y-auto"), rows()))
}

Reading y.Get() in the component's body instead rebuilds the component on every scrolled frame; use h.OnScroll for logic that runs once per frame of scrolling.

func UseValue

func UseValue[T any](initial T) *Value[T]

UseValue returns the Value of this hook slot: an observable value for something that changes often, such as a drag offset, a scroll position or download progress (ARCHITECTURE section 7, phase-deferred reads, as in Jetpack Compose). The first build stores initial; every build returns the same *Value.

Where v.Get() is called decides what a change redoes:

  • in the component's body while it builds: the component builds again, like with UseState (also when the components above it skip their build);
  • inside a layout-phase style closure (WidthFn, HeightFn): that element is laid out again, with no build and no style computation;
  • inside a paint-phase style closure (TranslateFn, OpacityFn): that element is repainted, with no build, style or layout;
  • anywhere else (event handlers, effects, goroutines): nothing; Get just returns the value.

The reads are taken again on every evaluation, so a closure that stops reading a Value stops being redone by it. A progress bar fed by a goroutine, which lays out the bar again without building Download:

func Download() tela.Node {
    progress := tela.UseValue(0.0) // from 0 to 1
    tela.UseEffect(func() func() {
        ctx, cancel := context.WithCancel(context.Background())
        go fetch(ctx, func(done float64) { progress.Set(done) }) // any goroutine
        return cancel
    }, nil)
    return Div(Class("h-2 w-64 overflow-hidden rounded-full bg-slate-200"),
        Div(Class("h-full bg-indigo-500"),
            Style(tela.WidthFn(func() float32 { return float32(256 * progress.Get()) }))))
}

A dragged card that moves by repainting only (the offset is set from pointer handlers):

type Offset struct{ X, Y float32 }

offset := tela.UseValue(Offset{})
card := Div(Class("rounded-xl bg-white p-6 shadow-lg"),
    Style(tela.TranslateFn(func() (x, y float32) {
        p := offset.Get()
        return p.X, p.Y
    })))

Reading the same Value in the body instead (x := offset.Get()) would build the component on every change. Values read by the closures of another window's tree are not tracked.

type WheelEvent

type WheelEvent = core.WheelEvent

WheelEvent is passed to h.OnWheel: a mouse wheel notch or a touchpad scroll over the element (the DOM's wheel event). DeltaX and DeltaY are the distance in logical pixels with the DOM's signs (positive DeltaY scrolls down; a notch is 53 pixels, and Shift turns a vertical notch horizontal); X, Y, LocalX and LocalY and the modifiers are as in MouseEvent. It bubbles, and its default action scrolls the nearest scrolling element that can move that way: PreventDefault cancels it.

// Ctrl+wheel zooms a canvas instead of scrolling the page.
Div(OnWheel(func(e tela.WheelEvent) {
    if e.Ctrl {
        e.PreventDefault()
        zoom.Update(func(z float32) float32 { return z * (1 - e.DeltaY/500) })
    }
}))

Directories

Path Synopsis
cmd
tela command
Command tela is the Tela command line tool.
Command tela is the Tela command line tool.
examples
board command
Command board is a small kanban board: three columns of cards that can be dragged from one column to another, or reordered inside a column.
Command board is a small kanban board: three columns of cards that can be dragged from one column to another, or reordered inside a column.
counter command
Command counter is the Counter example of ARCHITECTURE section 1: a card with a heading and a button, styled only with Tailwind classes.
Command counter is the Counter example of ARCHITECTURE section 1: a card with a heading and a button, styled only with Tailwind classes.
dashboard command
Command dashboard is an admin dashboard in the style of shadcn/ui's dashboard example: a header with navigation, four stat cards, a bar chart built from divs and a list of recent sales with avatar initials.
Command dashboard is an admin dashboard in the style of shadcn/ui's dashboard example: a header with navigation, four stat cards, a bar chart built from divs and a list of recent sales with avatar initials.
hello command
Command hello is the smallest Tela program that looks like something: a static screen with a headline, a tagline and a rule, styled only with Tailwind classes.
Command hello is the smallest Tela program that looks like something: a static screen with a headline, a tagline and a rule, styled only with Tailwind classes.
native command
Command native is a file explorer that looks native on Windows 11 (Fluent), GNOME (Adwaita) and KDE (Breeze), each in light and dark.
Command native is a file explorer that looks native on Windows 11 (Fluent), GNOME (Adwaita) and KDE (Breeze), each in light and dark.
player command
Command player is a music player: a card with cover art, title, artist, a progress bar, previous/play/next buttons, and a queue of tracks.
Command player is a music player: a card with cover art, title, artist, a progress bar, previous/play/next buttons, and a queue of tracks.
pricing command
Command pricing is a pricing page: three plans with a highlighted one, feature lists with check marks and a monthly/yearly switch that changes the prices.
Command pricing is a pricing page: three plans with a highlighted one, feature lists with check marks and a monthly/yearly switch that changes the prices.
settings command
Command settings is a preferences page: a notifications card with switches, an appearance card with radio-like option cards, and a footer with Reset and Save buttons that stay disabled until something changed.
Command settings is a preferences page: a notifications card with switches, an appearance card with radio-like option cards, and a footer with Reset and Save buttons that stay disabled until something changed.
shapes command
Command shapes is a showcase of what Tela's styling can paint: a page of twelve cards with shadows, gradients (linear, radial and conic), rings, blur and backdrop blur, opacity, rounded and rotated shapes, all written as elements and Tailwind classes.
Command shapes is a showcase of what Tela's styling can paint: a page of twelve cards with shadows, gradients (linear, radial and conic), rings, blur and backdrop blur, opacity, rounded and rotated shapes, all written as elements and Tailwind classes.
signup command
Command signup is a "Create your account" screen: a brand panel on the left and, on the right, a form with every kind of text field Tela has.
Command signup is a "Create your account" screen: a brand panel on the left and, on the right, a form with every kind of text field Tela has.
text command
Command text is the phase-2 showcase: mixed-script paragraphs (Latin, Arabic, CJK, emoji), a code block, truncation and decorations, laid out on a page of cards in a native window.
Command text is the phase-2 showcase: mixed-script paragraphs (Latin, Arabic, CJK, emoji), a code block, truncation and decorations, laid out on a page of cards in a native window.
virtuallist command
Command virtuallist is a directory of 100,000 people in an h.VirtualList: only the rows in view (plus a few) are built, laid out and painted, rows with a note are taller and measured when they appear, and the header's buttons jump to a row with ElementRef.ScrollToIndex.
Command virtuallist is a directory of 100,000 people in an h.VirtualList: only the rows in view (plus a few) are built, laid out and painted, rows with a note are taller and measured when they appear, and the header's buttons jump to a row with ElementRef.ScrollToIndex.
h
Package h holds the elements, props and helpers of a Tela UI.
Package h holds the elements, props and helpers of a Tela UI.
internal
assets/fonts
Package fonts embeds the fonts Tela ships with, as raw font file bytes.
Package fonts embeds the fonts Tela ships with, as raw font file bytes.
core
Package core is Tela's runtime (ARCHITECTURE sections 6 to 8): the three trees, reconciliation, hooks, the frame pipeline and its scheduler, and the hosts that run a tree in a window or headless.
Package core is Tela's runtime (ARCHITECTURE sections 6 to 8): the three trees, reconciliation, hooks, the frame pipeline and its scheduler, and the hosts that run a tree in a window or headless.
fmath
Package fmath holds the elementary functions that every layer feeding the rasterizer needs with the same result, bit for bit, on every architecture (ADR-027, ADR-041).
Package fmath holds the elementary functions that every layer feeding the rasterizer needs with the same result, bit for bit, on every architecture (ADR-027, ADR-041).
gen/keys command
Command keys generates keys_gen.go in package tela: a tela.Key constant for every named key of internal/platform (its Key constants) and a tela.Code constant for every physical key code (its KeyCode constants and their W3C names), so the public lists never drift from the platform's.
Command keys generates keys_gen.go in package tela: a tela.Key constant for every named key of internal/platform (its Key constants) and a tela.Code constant for every physical key code (its KeyCode constants and their W3C names), so the public lists never drift from the platform's.
gen/wikidoc command
Command wikidoc generates the API reference pages of the GitHub wiki (docs/wiki/API-Reference-<pkg>.md) from the doc comments of Tela's public packages, so the wiki never drifts from pkg.go.dev.
Command wikidoc generates the API reference pages of the GitHub wiki (docs/wiki/API-Reference-<pkg>.md) from the doc comments of Tela's public packages, so the wiki never drifts from pkg.go.dev.
gesture
Package gesture is Tela's gesture arena (ARCHITECTURE section 14), after Flutter's: the recognizers interested in a pointer sequence (a press and what follows until the release) compete in that pointer's arena, and one wins.
Package gesture is Tela's gesture arena (ARCHITECTURE section 14), after Flutter's: the recognizers interested in a pointer sequence (a press and what follows until the release) compete in that pointer's arena, and one wins.
gfx
Package gfx holds Tela's basic graphics types and the DisplayList, the contract between the paint phase, the CPU rasterizer and any future GPU backend.
Package gfx holds Tela's basic graphics types and the DisplayList, the contract between the paint phase, the CPU rasterizer and any future GPU backend.
gfx/raster
Package raster is Tela's CPU rasterizer: it renders a gfx.DisplayList into an *image.RGBA.
Package raster is Tela's CPU rasterizer: it renders a gfx.DisplayList into an *image.RGBA.
layout
Package layout is Tela's layout engine (ARCHITECTURE section 9): block layout without margin collapsing, flexbox, relative and absolute positioning, intrinsic sizes, leaf measurement by callback, caching and relayout boundaries.
Package layout is Tela's layout engine (ARCHITECTURE section 9): block layout without margin collapsing, flexbox, relative and absolute positioning, intrinsic sizes, leaf measurement by callback, caching and relayout boundaries.
platform
Package platform is the lowest layer of Tela: the contract between the framework and one OS windowing system (a backend).
Package platform is the lowest layer of Tela: the contract between the framework and one OS windowing system (a backend).
platform/auto
Package auto selects the platform backend for the current environment.
Package auto selects the platform backend for the current environment.
platform/headless
Package headless is an in-memory platform backend for tests, snapshots, telatest and the CLI.
Package headless is an in-memory platform backend for tests, snapshots, telatest and the CLI.
platform/internal/evdev
Package evdev maps Linux evdev key codes (linux/input-event-codes.h) to physical keys (platform.KeyCode, W3C "code" values).
Package evdev maps Linux evdev key codes (linux/input-event-codes.h) to physical keys (platform.KeyCode, W3C "code" values).
platform/internal/keyboard
Package keyboard holds the OS-independent part of the platform keyboard contract (see package platform, "Keyboard"), shared by the backends: the set of keys a window has delivered presses for, which turns into synthetic releases when the window loses focus, and the reporting of keyboard setup errors.
Package keyboard holds the OS-independent part of the platform keyboard contract (see package platform, "Keyboard"), shared by the backends: the set of keys a window has delivered presses for, which turns into synthetic releases when the window loses focus, and the reporting of keyboard setup errors.
platform/internal/loop
Package loop is the OS-independent half of a platform backend's event loop: the part of the platform contract (see package platform, "Loop contract" and "Frames and pacing") that does not depend on how the OS reports events.
Package loop is the OS-independent half of a platform backend's event loop: the part of the platform contract (see package platform, "Loop contract" and "Frames and pacing") that does not depend on how the OS reports events.
platform/internal/utf16x
Package utf16x converts text offsets between UTF-16 code units and UTF-8 bytes for the backends whose OS text APIs count UTF-16 (Win32 IMM32 and macOS NSTextInputClient).
Package utf16x converts text offsets between UTF-16 code units and UTF-8 bytes for the backends whose OS text APIs count UTF-16 (Win32 IMM32 and macOS NSTextInputClient).
platform/internal/xkb
Package xkb is a minimal pure-Go implementation of the parts of XKB a Wayland client needs (no libxkbcommon, no cgo):
Package xkb is a minimal pure-Go implementation of the parts of XKB a Wayland client needs (no libxkbcommon, no cgo):
platform/platformtest
Package platformtest is a backend-agnostic conformance suite for the platform contract.
Package platformtest is a backend-agnostic conformance suite for the platform contract.
platform/wayland
Package wayland is Tela's Wayland platform backend: a pure-Go client of the compositor that implements the platform contract (see package platform) over the wire runtime and the generated protocol bindings.
Package wayland is Tela's Wayland platform backend: a pure-Go client of the compositor that implements the platform contract (see package platform) over the wire runtime and the generated protocol bindings.
platform/wayland/protocols
Package protocols holds the Go bindings of the Wayland protocols Tela uses, generated from the XML files in xml/ by internal/platform/wayland/wlgen.
Package protocols holds the Go bindings of the Wayland protocols Tela uses, generated from the XML files in xml/ by internal/platform/wayland/wlgen.
platform/wayland/wire
Package wire is the client side of the Wayland wire protocol: connection setup, the object ID map, message encoding and decoding, file-descriptor passing and buffered, non-blocking I/O.
Package wire is the client side of the Wayland wire protocol: connection setup, the object ID map, message encoding and decoding, file-descriptor passing and buffered, non-blocking I/O.
platform/wayland/wlgen command
Command wlgen generates Go bindings from Wayland protocol XML files.
Command wlgen generates Go bindings from Wayland protocol XML files.
platform/win32
Package win32 is the Windows platform backend.
Package win32 is the Windows platform backend.
platform/x11
Package x11 is the X11 platform backend, the Linux fallback when no Wayland compositor is available.
Package x11 is the X11 platform backend, the Linux fallback when no Wayland compositor is available.
platform/x11/internal/xgb
Package XGB provides the X Go Binding, which is a low-level API to communicate with the core X protocol and many of the X extensions.
Package XGB provides the X Go Binding, which is a low-level API to communicate with the core X protocol and many of the X extensions.
platform/x11/internal/xgb/bigreq
Package bigreq is the X client API for the BIG-REQUESTS extension.
Package bigreq is the X client API for the BIG-REQUESTS extension.
platform/x11/internal/xgb/xproto
Package xproto is the X client API for the extension.
Package xproto is the X client API for the extension.
semantics
Package semantics holds Tela's semantics tree (ARCHITECTURE sections 4, 8 and 16): a parallel tree of roles, names, states and actions derived from the Element and Box trees, used by accessibility backends, tests (telatest's ByRole and ByText) and agents (the aria snapshot of `tela inspect --aria` and `tela mcp`).
Package semantics holds Tela's semantics tree (ARCHITECTURE sections 4, 8 and 16): a parallel tree of roles, names, states and actions derived from the Element and Box trees, used by accessibility backends, tests (telatest's ByRole and ByText) and agents (the aria snapshot of `tela inspect --aria` and `tela mcp`).
style
Package style is Tela's style layer (ARCHITECTURE section 10): the theme and design tokens, the Tailwind v4 class parser, and the resolution of classes and typed props into a ComputedStyle.
Package style is Tela's style layer (ARCHITECTURE section 10): the theme and design tokens, the Tailwind v4 class parser, and the resolution of classes and typed props into a ComputedStyle.
style/gen command
Command gen generates internal/style/theme_gen.go, the default theme, from the Tailwind v4 theme source vendored in tailwind/theme.css (ADR-018, ADR-037).
Command gen generates internal/style/theme_gen.go, the default theme, from the Tailwind v4 theme source vendored in tailwind/theme.css (ADR-018, ADR-037).
testutil/golden
Package golden compares images produced by tests against golden PNG files.
Package golden compares images produced by tests against golden PNG files.
text
Package text is Tela's text layer (ARCHITECTURE section 11): the font layer (typefaces, face instances, metrics, font matching and fallback, color glyphs) and, on top of it, paragraph layout (shaping, bidi, line breaking, alignment, truncation, painting); see "Paragraph layout".
Package text is Tela's text layer (ARCHITECTURE section 11): the font layer (typefaces, face instances, metrics, font matching and fallback, color glyphs) and, on top of it, paragraph layout (shaping, bidi, line breaking, alignment, truncation, painting); see "Paragraph layout".
text/edit
Package edit is the editable plain-text model under core's Input and Textarea (ARCHITECTURE section 11, ADR-065): a UTF-8 value, a selection, an IME composition, an undo history and the commands that key and pointer handling produce.
Package edit is the editable plain-text model under core's Input and Textarea (ARCHITECTURE section 11, ADR-065): a UTF-8 value, a selection, an IME composition, an undo history and the commands that key and pointer handling produce.
Package telatest tests Tela UIs without a window: it mounts a tree in a headless window, simulates a user with real pointer and key events, finds elements by role and text like Playwright, and compares the pixels with goldens (ARCHITECTURE section 17).
Package telatest tests Tela UIs without a window: it mounts a tree in a headless window, simulates a user with real pointer and key events, finds elements by role and text like Playwright, and compares the pixels with goldens (ARCHITECTURE section 17).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL