Skip to content

API Reference tela

Andres Felipe Martinez Salazar edited this page Nov 2, 2026 · 10 revisions

API reference: package tela

import "github.com/kuronosu/tela"

This page is generated from the doc comments of the package; the same text is on pkg.go.dev. For guides and examples, start at the wiki home.

Index

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 components: struct values with a Build method (Component) and functions. Elements (Div, Button, Text...) live in the companion package h, designed to be dot-imported; this package holds the application (App, Main, Run), hooks and component boundaries (Component, UseState, C, CP...), events (MouseEvent, PointerEvent, DragEvent, KeyboardEvent with the KeyName and KeyCode 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 { return tela.C(counter) }

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.Main(tela.App{Title: "Demo", Width: 800, Height: 600, Root: Counter()})
}

Main runs the app and, if no window can be opened, prints the error and exits with status 1; Run returns the error instead.

Styling

Static styling uses Tailwind v4 classes (h.Class). A class that depends on state goes in h.If, which takes props as well as nodes, and dynamic values go through typed props, which apply after the classes:

Div(Class("rounded-xl p-4"), If(selected, Class("ring-2 ring-indigo-500")),
    Style(tela.Width(w), tela.Bg(tela.Hex("#1e1e2e"))))

h.Class takes several strings and joins them with a space, and a StyleProp is also a child, so Div(Class("p-4", tone), tela.Width(w)) needs neither concatenation nor Style; WH and Box set several at once.

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 (MapValue and h.Scale compute from one). Colors are extended sRGB; see Color.

Components

A component is a struct whose Build method returns a Node: its fields are its props, and children are an ordinary field of type Component (several of them through h.Fragment). Every component value given to an element is a component boundary: an element with its own hook state, rebuilt on its own when that state changes, and skipped when its parent builds again with an equal value and nothing it reads changed (automatic skipping, as in Jetpack Compose).

type Card struct {
    Title    string
    OnOpen   tela.Callback[func()]
    Children tela.Component
}

func (c Card) Build() tela.Node {
    n, setN := tela.UseState(0)
    return Div(Class("rounded-xl border p-4"),
        H2(Text(c.Title)),
        Button(OnClick(func(tela.MouseEvent) { setN.Set(n + 1) }), Textf("Opened %d times", n)),
        c.Children)
}

Div(Card{Title: "Inbox", Children: Fragment(Text("a"), Span(Text("b")))})

Build has a value receiver. The identity of a component among its siblings is its type and its key (WithKey), and the comparison that skips its build never panics: see Component.

A function returning a Node is a component too when it is wrapped in C (no props) or CP (comparable props); CP skips like a struct component, and Memo does the same with a comparison function. Write the wrap where the component is defined, in a public function over a private one, so that nobody who uses it writes C or CP:

func Header() tela.Node { return tela.C(header) }

func Badge(p BadgeProps) tela.Node { return tela.CP(badge, p) }

Div(Header(), Badge(BadgeProps{Count: 3}))

Calling Build, or a component function, directly runs it inside the caller, whose hook slots its hooks would take: in dev mode a hook called from a function that is neither the component being built (its Build, its function, or a function literal inside them) nor a custom hook panics with the fix. A custom hook is a function named UseXxx or useXxx that calls hooks for the component that calls it; a struct of Value pointers is a good thing for one to return, since it is stable and comparable:

type Drag struct{ X, Y *tela.Value[float32] }

func UseDrag() Drag {
    return Drag{X: tela.UseValue(float32(0)), Y: tela.UseValue(float32(0))}
}

A plain function without hooks (a wrapper that styles its children) needs no boundary; h.Fragment(children...) passes the children it receives through.

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, when their dependencies change; UseMount runs once, on mount (with its cleanup on unmount), UseEffectDo has no cleanup and UseUpdateEffect skips the first build.
  • UseInterval calls a function every period on the UI thread, UseTimeout once after a delay, and UseGo runs a function on its own goroutine with a context that is cancelled when its dependencies change or the component unmounts.
  • UseMemo caches a computation and UseOnce computes a value once; 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, except on X11), 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(float32(0)) // UseValue(0.0) would hold a float64
Div(Class("size-8 rounded-full bg-indigo-500"),
    Style(tela.TranslateFn(func() (x, y float32) { return pos.Get(), 0 })))
// later, from anywhere: pos.Set(120)

Hooks follow React's rules: call them at the top level of a component's Build or function, in the same order on every build, never inside conditions or loops. A hook called outside a component build panics with an explanation (a component called where a node is expected is told to use C, or to pass the struct value); in dev mode 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.

Dev mode

Dev mode reports the mistakes that compile. It is on unless the program is a release build (built with -trimpath, or installed with go install pkg@version; go run and go build in a source checkout are development builds); App.Dev overrides that, and TELA_DEV=1 or TELA_DEV=0 overrides both. telatest runs in dev mode unless telatest.NoDev says otherwise. In dev mode:

  • unknown classes are printed on standard error with the closest class (in telatest they fail the test);
  • unknown roles, input types and easings, hook-order changes, hooks called outside their component, a pointer given as a Component, a Prop in a field of type Component and a nil dependency list (UseEffect(f, nil): use Once or UseMount) panic with the fix;
  • likely mistakes are printed as warnings, once per call site: lists without keys or with duplicate keys, dependencies that cannot be compared, a Value read with Get in a handler while changes are queued (use Latest), a context read without a Provider whose default is not safe to use, and an h.Label that labels no field.

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 of Preview values, 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. A package-level var App = tela.App{...} gives both the window's size, root class, color scheme, theme and class aliases, so the image matches the window; telatest.MountApp mounts the same App in a test. See the documentation of cmd/tela.

Stability

Node, Component, 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, KeyName, KeyCode, 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.

Constants

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.

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.

const (
    // DropNone refuses a drop, or reports that a drag was refused or
    // cancelled.
    DropNone = core.DropNone
    // DropCopy copies the data.
    DropCopy = core.DropCopy
    // DropMove moves the data: the source deletes its copy.
    DropMove = core.DropMove
    // DropLink links to the data.
    DropLink = core.DropLink
    // DropAll is every effect (HTML's effectAllowed "all"), what a source
    // allows unless it says otherwise.
    DropAll = core.DropAll
)

The drop effects.

const (
    // MIMEText is UTF-8 plain text ("text/plain" and HTML's "text" name it
    // too).
    MIMEText = core.MIMEText
    // MIMEHTML is an HTML fragment in UTF-8.
    MIMEHTML = core.MIMEHTML
    // MIMEPNG is a PNG image.
    MIMEPNG = core.MIMEPNG
    // MIMEURIList is a list of URIs: files are file: URIs
    // (DataTransfer.Files). HTML's "Files" and "url" name it too.
    MIMEURIList = core.MIMEURIList
)

The MIME types every platform maps onto its own formats (DataTransfer). Any other MIME type is a custom type ("application/x-myapp-card"), carried by name.

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).

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.

const (
    // WindowMinimize minimizes the window.
    WindowMinimize = core.WindowMinimize
    // WindowMaximize maximizes the window, or restores it when it is
    // maximized.
    WindowMaximize = core.WindowMaximize
    // WindowClose asks to close the window, as its close button does.
    WindowClose = core.WindowClose
)
const (
    // BackdropDefault leaves the choice to the system: no material for a
    // plain window.
    BackdropDefault = core.BackdropDefault
    // BackdropNone turns the system material off.
    BackdropNone = core.BackdropNone
    // BackdropMica is Mica, the opaque material tinted with the desktop
    // wallpaper, for the main windows of an app.
    BackdropMica = core.BackdropMica
    // BackdropMicaAlt is Mica Alt, a stronger tint, for windows with tabs
    // in their title bar.
    BackdropMicaAlt = core.BackdropMicaAlt
    // BackdropAcrylic is Acrylic, the translucent blurred material of
    // transient surfaces such as flyouts.
    BackdropAcrylic = core.BackdropAcrylic
)

Variables

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.

var Once = core.Once

Once is the dependency of a hook that runs or computes only on the first build (React's empty dependency list []):

tela.UseEffect(f, tela.Once)       // after the first build; cleanup on unmount
tela.UseLayoutEffect(f, tela.Once) // the same, before the first paint
tela.UseGo(f, tela.Once)           // one goroutine for the component's lifetime
tela.UseMemo(f, tela.Once)         // computed once

Its type is unexported, so it is equal to itself on every build and is never confused with a value of yours. UseMount and UseOnce are the shorthands for effects and values. It replaces a single nil dependency (UseEffect(f, nil)): React reads a null list as "after every render", so dev mode panics with this fix, and outside dev mode nil keeps meaning "only on mount". With UseUpdateEffect, which skips the first build, Once never runs (dev mode warns).

Functions

func Animate

func Animate[T Animatable](v *Value[T], target T, opts ...AnimationOption)

Animate moves v to target: with a spring by default (Motion's default, or as Stiffness, Damping, Bounce or Spring say), or with a tween when the options are Duration and Ease only. v is any Value of an Animatable type, from UseValue, UseSpring or UseTween.

y := tela.UseValue[float32](0)
Button(OnClick(func(tela.MouseEvent) { tela.Animate(y, 200, tela.Spring(tela.Bounce(0.25))) }),
    Text("Drop"))
tela.Animate(y, 0, tela.Duration(300*time.Millisecond), tela.Ease("ease-in-out"))

A new Animate on the same Value interrupts the running animation: the new one starts where the value is and, for a spring, at the speed it has, so a fling continues smoothly; a Set or Update of the Value stops the animation. Like Set, Animate is safe from any goroutine: the animation starts when the UI thread applies it, in order with the Value's Set and Update calls (an animation applied between two frames starts at the next one). Value.Latest does not anticipate animations: it returns the value they have reached.

With reduced motion, or in a tree that no frame clock drives, the value jumps to target. An animation of a Value whose component unmounts ends.

Example

t := &exampleT{}
defer t.close()
var y *tela.Value[float32]
ball := func() tela.Node {
    y = tela.UseValue[float32](0)
    return Div(
        Button(OnClick(func(tela.MouseEvent) { tela.Animate(y, 200, tela.Spring(tela.Bounce(0.25))) }), Text("Drop")),
        Button(OnClick(func(tela.MouseEvent) {
            tela.Animate(y, 0, tela.Duration(300*time.Millisecond), tela.Ease("ease-in-out"))
        }), Text("Lift")),
        Div(Class("size-8 rounded-full bg-indigo-500"), Style(tela.TranslateFn(func() (float32, float32) { return 0, y.Get() }))))
}
app := telatest.Mount(t, tela.C(ball))
app.Click(telatest.ByRole("button", "Drop")) // settles: the spring ends
fmt.Println(y.Get())
app.Click(telatest.ByRole("button", "Lift"))
fmt.Println(y.Get())
// Output:
// 200
// 0

func Assets

func Assets(fsys fs.FS)

Assets registers a file system that images are loaded from: the path in a class such as bg-[url(img/hero.jpg)] or mask-[url(icons/star.svg)], or in h.SrcPath("img/logo.svg"), names a file of fsys, relative to its root (a leading "/" or "./" is ignored, so url(/img/hero.jpg) is the same file; ".." is not allowed). Call it from an init function, so that the files are found the same way by Run, tela snapshot, tela inspect and telatest:

//go:embed img
var images embed.FS

func init() { tela.Assets(images) }

Assets may be called several times: the file systems are searched in the order they were registered and the first one that has the file wins. Files are decoded once, when first used, and kept for the life of the process: PNG, JPEG, GIF (the first frame), WebP and SVG (drawn as vectors), recognized by their content. A missing or undecodable file paints nothing and is reported once in the dev log, with the path and the registered file systems, like an unknown class. Remote and data: URLs are not supported.

Example

// An app embeds its images and registers them from an init function:
//
//	//go:embed img
//	var images embed.FS
//
//	func init() { tela.Assets(images) }
//
// Here an in-memory file system holds one red PNG.
tela.Assets(fstest.MapFS{"img/red.png": {Data: solidPNG(color.RGBA{R: 255, A: 255})}})

// With package h: Div(Class("size-8 bg-[url(img/red.png)] bg-cover")).
card := core.Primitive(style.KindDiv, core.Class("size-8 bg-[url(img/red.png)] bg-cover"))
_, img := core.Render(card, core.RenderOptions{Width: 32, Height: 32})
fmt.Println(img.RGBAAt(16, 16))
// Output: {255 0 0 255}

func DevMode

func DevMode() bool

DevMode reports whether dev mode is on where it is called: in a build, the mode of the tree being built (App.Dev, telatest.NoDev); elsewhere the process default, on unless the program is a release build, with TELA_DEV=1 or TELA_DEV=0 overriding both (ADR-085, ADR-093). Component libraries use it to check their own props the way Tela checks classes and roles: panic with the fix in dev mode, fall back quietly otherwise. It is a lookup and costs nothing.

func Main

func Main(app App)

Main runs app with Run and, when Run returns an error (no display, for example), prints it on standard error and exits with status 1. It is the usual body of main:

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

where Counter is a function that returns tela.C(counter). Main returns when the window is closed. Like Run, it must be called from the main goroutine, and it panics when app.Root is nil.

Example

counter := func() tela.Node {
    count, setCount := tela.UseState(0)
    return Button(OnClick(func(tela.MouseEvent) { setCount.Set(count + 1) }), Textf("Clicked %d times", count))
}
// In package main. Main returns when the window is closed; without a
// display it prints the error and exits with status 1.
tela.Main(tela.App{Title: "Demo", Width: 400, Height: 300, Root: tela.C(counter)})

func MapValue

func MapValue[T, U any](v *Value[T], f func(T) U) func() U

MapValue returns a function that reads v and applies f, for the deferred style props (WidthFn, HeightFn, OpacityFn, BgFn, TextColorFn), which take a func with one result: v is read, and tracked, when the prop is evaluated in a later phase, not when the component builds.

Style(tela.HeightFn(tela.MapValue(open, func(t float32) float32 { return 36 + 120*t })))
Style(tela.BgFn(tela.MapValue(progress, func(p float32) tela.Color { return mix(from, to, p) })))

h.Scale is MapValue for a float32 times a constant.

Example

t := &exampleT{}
defer t.close()
var open *tela.Value[float32]
panel := func() tela.Node {
    open = tela.UseValue(float32(0))
    // 36 px closed, 156 px open: only the box is laid out again when
    // open changes, the component does not build.
    return Div(TestID("panel"), Class("w-40 overflow-hidden bg-slate-100"),
        Style(tela.HeightFn(tela.MapValue(open, func(t float32) float32 { return 36 + 120*t }))))
}
app := telatest.Mount(t, tela.C(panel))
fmt.Println(app.Bounds(telatest.ByTestID("panel")).Height)
tela.Animate(open, 1, tela.Duration(100*time.Millisecond))
app.Advance(time.Second)
fmt.Println(app.Bounds(telatest.ByTestID("panel")).Height)
// Output:
// 36
// 156

func OpenPath

func OpenPath(path string) error

OpenPath opens a local file in its default application, or a folder in the file manager. Call it on the UI thread (a handler, an effect); from another goroutine, wrap it in Post.

func OpenURL

func OpenURL(url string) error

OpenURL opens url in the user's default application for its scheme: a web page in the browser, a mailto: address in the mail client. The URL must have a scheme ("https://example.com"); open local files and folders with OpenPath. The error reports a URL that cannot be opened or a desktop without the service (headless). Call it on the UI thread (a handler, an effect); from another goroutine, wrap it in Post.

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 Quit

func Quit()

Quit quits the app: Run returns (and Main with it) after unmounting every window, which runs the cleanups of their effects. It is safe from any goroutine and is applied in order with the state updates queued before it.

func Run

func Run(app App) error

Run opens a window, shows app.Root and runs the event loop until the app quits: the main window is closed (unless App.OnClose says otherwise) or Quit is called. The windows unmount before Run returns, running the cleanups of their effects. 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 on Windows and Cocoa on macOS 13 or later; Window components open more windows.

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

func SetColorScheme

func SetColorScheme(s ColorScheme)

SetColorScheme sets the color scheme of the running app: Light or Dark override the system's preference, System follows it again. Every element is styled again (dark: classes) and the components that call UseColorScheme build again. It is safe from any goroutine, like a state setter: the change applies on the UI thread before the next frame. Called before Run, it applies when the window opens (it wins over App.ColorScheme). Like Post, it goes to the app that receives posted functions (telatest.Mount's app in tests).

Button(OnClick(func(tela.MouseEvent) { tela.SetColorScheme(tela.Dark) }), Text("Dark"))

It panics for a value other than System, Light and Dark.

Example

toggle := func() tela.Node {
    dark := tela.UseColorScheme() == tela.Dark
    next, label := tela.Dark, "Dark mode"
    if dark {
        next, label = tela.Light, "Light mode"
    }
    return Button(Class("bg-white text-black dark:bg-black dark:text-white"),
        OnClick(func(tela.MouseEvent) { tela.SetColorScheme(next) }), Text(label))
}
app := telatest.Mount(t, tela.C(toggle))
defer app.Close()
app.Click(telatest.ByRole("button", "Dark mode"))
fmt.Println(app.AriaSnapshot())
// Output:
// - button "Light mode"

func SetTheme

func SetTheme(t Theme)

SetTheme replaces the theme of the running app: classes are compiled again against it (with the app's ClassAliases), every element is styled again and the components that call UseTheme build again. Like SetColorScheme it is safe from any goroutine, applies on the UI thread before the next frame, goes to the app that receives posted functions, and called before Run applies when the window opens (it wins over App.Theme). It panics for a token name classes cannot use.

func Toggle

func Toggle(set Setter[bool]) func()

Toggle returns a function that flips set's state, for a handler: OnClick(tela.Toggle(setOpen)). It uses Setter.Update, so two clicks that land in one frame both count and the state ends where it started, which setOpen.Set(!open) does not give (both clicks read the same open).

open, setOpen := tela.UseState(false)
Button(OnClick(tela.Toggle(setOpen)), Text("Menu"))

Example

t := &exampleT{}
defer t.close()
menu := func() tela.Node {
    open, setOpen := tela.UseState(false)
    return Div(
        Button(OnClick(tela.Toggle(setOpen)), Text("Menu")),
        If(open, P(Text("Settings"))))
}
app := telatest.Mount(t, tela.C(menu))
app.Click(telatest.ByRole("button", "Menu"))
fmt.Println(app.Count(telatest.ByText("Settings")))
app.Click(telatest.ByRole("button", "Menu"))
fmt.Println(app.Count(telatest.ByText("Settings")))
// Output:
// 1
// 0

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)

In dev mode, reading a context without a Provider above warns (once per context) when its default is not safe to use: nil, or holding a nil function, pointer, map, channel or interface, or a zero Setter or Callback, in an exported field. Either provide the context above every reader, or give CreateContext a default that works; IsZero tells a zero Setter or Callback apart where a missing Provider is expected.

func UseDirection

func UseDirection() string

UseDirection returns the writing direction at the component's position, "ltr" or "rtl": the one the nearest element above it with an h.Dir prop gives, else "ltr". Under h.Dir("auto") it is the direction that element resolves from its text; the first build of a component below an auto element that has not been styled yet reads "ltr", and when the element resolves to "rtl" the component builds again before the frame is laid out, so the wrong direction never reaches the screen. Content of an h.Portal follows the position where the Portal is declared. The component builds again when the direction changes (a Dir prop above it, or the text of an auto element), like with a state hook; nothing else does. It is a hook: call it at the top level of a component.

Classes do not need it: the logical utilities (ms-*, ps-*, start-*, rounded-s-*...) and the rtl: and ltr: variants already follow the direction. Use it for what classes cannot do, such as the arrow keys or the drag of a slider:

func Stepper() tela.Node {
    next := "ArrowRight"
    if tela.UseDirection() == "rtl" {
        next = "ArrowLeft"
    }
    ...
}

It is a string, the vocabulary of h.Dir and of Radix's useDirection, so the value passes as it is to h.Dir and to the Direction fields of kit and aria.

Example

arrow := func() tela.Node {
    next := "ArrowRight"
    if tela.UseDirection() == "rtl" {
        next = "ArrowLeft"
    }
    return h.P(h.Textf("Next: %s", next))
}
app := func() tela.Node {
    return h.Div(tela.C(arrow), h.Div(h.Dir("rtl"), tela.C(arrow)))
}
yaml, _ := telatest.AriaSnapshot(tela.C(app))
fmt.Println(yaml)
// Output:
// - paragraph: "Next: ArrowRight"
// - paragraph: "Next: ArrowLeft"

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, id, n)     // when id or n changed (compared with ==)
tela.UseEffect(f, tela.Once) // only after the first build (React's [])
tela.UseMount(f)             // the same

For an effect that runs once, on mount, with its cleanup on unmount (a subscription), use UseMount or Once; UseEffectDo takes an effect without a cleanup, UseUpdateEffect skips the first build, and periodic and background work have UseInterval and UseGo. A single nil dependency, UseEffect(f, nil), is rejected in dev mode with the fix (Once: React reads a null list as "after every render"); outside dev mode it keeps meaning "only on mount".

Values that cannot be compared (functions, slices, maps) count as changed on every build; dev mode 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 UseEffectDo

func UseEffectDo(f func(), deps ...any)

UseEffectDo is UseEffect for an effect without a cleanup: f runs after the frame is presented when the dependencies changed.

tela.UseEffectDo(func() { title.Set(fmt.Sprintf("%d unread", n)) }, n)

Example

t := &exampleT{}
defer t.close()
var title string
inbox := func() tela.Node {
    unread, setUnread := tela.UseState(3)
    tela.UseEffectDo(func() { title = fmt.Sprintf("Inbox (%d)", unread) }, unread)
    return Button(OnClick(func(tela.MouseEvent) { setUnread.Set(0) }), Text("Mark all read"))
}
app := telatest.Mount(t, tela.C(inbox))
fmt.Println(title)
app.Click(telatest.ByRole("button", "Mark all read"))
fmt.Println(title)
// Output:
// Inbox (3)
// Inbox (0)

func UseGo

func UseGo(f func(ctx context.Context), deps ...any)

UseGo runs f on a goroutine of its own after the frame is presented, when its dependencies changed, like UseEffect; ctx is cancelled at cleanup, when the dependencies change again or the component unmounts, before the next run starts. f must return soon after ctx is done (it is not waited for) and must change the UI only through what is safe from any goroutine: a Setter, a Value, Post.

tela.UseGo(func(ctx context.Context) {
    for page := range fetchPages(ctx, query) {
        setResults.Update(func(r []Result) []Result { return append(r, page...) })
    }
}, query) // restarts, cancelling the old fetch, when query changes

The dependencies follow UseEffect's: none restarts f after every build, and Once runs one goroutine for the lifetime of the component.

Example

t := &exampleT{}
defer t.close()
search := func(ctx context.Context, q string) []string {
    return []string{q + " one", q + " two"} // a slow call that honors ctx
}
results := func() tela.Node {
    query, setQuery := tela.UseState("tela")
    found, setFound := tela.UseState([]string(nil))
    tela.UseGo(func(ctx context.Context) {
        r := search(ctx, query)
        if ctx.Err() == nil { // a newer query cancelled this one
            setFound.Set(r) // safe from any goroutine
        }
    }, query)
    return Div(
        Button(OnClick(func(tela.MouseEvent) { setQuery.Set("go") }), Text("Search go")),
        Map(found, func(s string) tela.Node { return P(Key(s), Text(s)) }),
    )
}
app := telatest.Mount(t, tela.C(results))
app.ExpectText("tela two") // expectations wait for the goroutine
app.Click(telatest.ByRole("button", "Search go"))
app.ExpectText("go one")
fmt.Println("ok")
// Output: ok

func UseHighContrast

func UseHighContrast() bool

UseHighContrast reports whether the system asks for high contrast (Windows' contrast themes, GNOME's high contrast): the contrast-more: classes apply. The component builds again when it changes. It is a hook: call it at the top level of a component.

Classes do not need it: border-border contrast-more:border-foreground already follows the setting.

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()
return Div(Label(For(id), Text("Email")), Input(ID(id)))

func UseInterval

func UseInterval(d time.Duration, f func())

UseInterval calls f on the UI thread every d, the first time d after the frame that first shows the component, until it unmounts. d <= 0 pauses it, and a new d restarts it. f is always the latest build's function, so it sees the latest state and needs no dependencies. It runs on the window's frame clock, like the long-press timer, with no goroutine: telatest's App.Advance moves it, and a headless Render never runs it.

now, setNow := tela.UseState(time.Now())
tela.UseInterval(time.Second, func() { setNow.Set(time.Now()) })

Example

t := &exampleT{}
defer t.close()
timer := func() tela.Node {
    seconds, setSeconds := tela.UseState(0)
    running, setRunning := tela.UseState(true)
    period := time.Second
    if !running {
        period = 0 // paused
    }
    tela.UseInterval(period, func() { setSeconds.Set(seconds + 1) })
    return Div(
        P(Textf("%d s", seconds)),
        Button(OnClick(func(tela.MouseEvent) { setRunning.Set(!running) }), Text("Pause")),
    )
}
app := telatest.Mount(t, tela.C(timer))
app.Advance(3 * time.Second) // the interval runs on the app's clock
app.ExpectText("3 s")
app.Click(telatest.ByRole("button", "Pause"))
app.Advance(5 * time.Second)
app.ExpectText("3 s")
fmt.Println("ok")
// Output: ok

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. The scrolls it asks for (ElementRef's ScrollTo, ScrollBy, ScrollIntoView and ScrollToIndex) use the layout of its frame, which is painted scrolled: on mount, and when the content just grew.

last := tela.UseElementRef() // on the newest message
tela.UseLayoutEffect(func() func() {
    last.ScrollIntoView() // the frame that shows a new message shows it in view
    return nil
}, len(messages))

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). For a value computed once, use UseOnce (or UseMemo(compute, tela.Once)).

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

func UseMount

func UseMount(effect func() func())

UseMount runs effect once, after the frame that first shows the component, and the cleanup it returns (nil for none) when the component unmounts: the idiom for subscriptions, listeners and other setup that lives as long as the component (React's useEffect(f, [])).

tela.UseMount(func() func() {
    unsubscribe := store.Subscribe(func(s State) { setState.Set(s) })
    return unsubscribe
})

Example

t := &exampleT{}
defer t.close()
var events []string
panel := func() tela.Node {
    tela.UseMount(func() func() {
        events = append(events, "subscribe")
        return func() { events = append(events, "unsubscribe") }
    })
    return Text("Panel")
}
page := func() tela.Node {
    open, setOpen := tela.UseState(true)
    return Div(
        Button(OnClick(func(tela.MouseEvent) { setOpen.Set(!open) }), Text("Toggle")),
        If(open, tela.C(panel)),
    )
}
app := telatest.Mount(t, tela.C(page))
app.Click(telatest.ByRole("button", "Toggle")) // unmounts the panel
app.Click(telatest.ByRole("button", "Toggle")) // mounts it again
fmt.Println(events)
// Output: [subscribe unsubscribe subscribe]

func UseNativeMenus

func UseNativeMenus() bool

UseNativeMenus reports whether the app asks for native menus (App.NativeMenus; every Window follows its app). It does not change while the app runs, so the component does not build again for it. It is a hook: call it at the top level of a component.

It is for component libraries: kit's ContextMenu and Menubar combine it with their own Native setting, which wins.

func UseOnce

func UseOnce[T any](init func() T) T

UseOnce returns init(), called on the first build only; later builds return the same value. Use it for what a component creates once and keeps: a client, a buffer, the state struct of a custom hook. It is UseMemo without dependencies; the value is not reactive (store what changes in UseState or UseValue).

cache := tela.UseOnce(func() map[string]Image { return map[string]Image{} })

Example

t := &exampleT{}
defer t.close()
created := 0
type cache struct{ hits map[string]int }
page := func() tela.Node {
    c := tela.UseOnce(func() *cache { created++; return &cache{hits: map[string]int{}} })
    n, setN := tela.UseState(0)
    c.hits["build"]++
    return Button(OnClick(func(tela.MouseEvent) { setN.Set(n + 1) }), Textf("Add %d", n))
}
app := telatest.Mount(t, tela.C(page))
app.Click(telatest.ByText("Add"))
app.Click(telatest.ByText("Add"))
fmt.Println("created", created, "times")
// Output: created 1 times

func UsePresence

func UsePresence() (present bool, done func())

UsePresence reports whether the component is present, for a component inside a child of an h.Presence: present is false while that child exits, after it was removed. The component then runs its own exit (an Animate, saving a draft...) and calls done when it is finished; the child unmounts once every component that used UsePresence called done and the transitions of its h.Exit classes ended. done is the same function on every build, safe from any goroutine, and does nothing while the component is present (a call from a previous exit that was cancelled is ignored). Outside any Presence, present is always true.

A toast that slides down and fades out before it unmounts: the tween to its exit position calls done when it arrives (OnDone; done does nothing while the toast is present, so the entrance may pass it too).

func Toast(msg string) tela.Node {
    present, done := tela.UsePresence()
    target := float32(0)
    if !present {
        target = 1
    }
    out := tela.UseTween(target, tela.Duration(200*time.Millisecond), tela.OnDone(done))
    return Div(Class("rounded-lg bg-slate-900 px-4 py-2 text-white"),
        Style(tela.TranslateFn(func() (float32, float32) { return 0, 40 * out.Get() }),
            tela.OpacityFn(func() float32 { return 1 - out.Get() })),
        Text(msg))
}

Presence(If(saved, tela.C(func() tela.Node { return Toast("Saved") })))

Example

t := &exampleT{}
defer t.close()
toast := func() tela.Node {
    present, done := tela.UsePresence()
    target := float32(0)
    if !present {
        target = 1
    }
    // The tween out ends the exit: done does nothing while present.
    out := tela.UseTween(target, tela.Duration(200*time.Millisecond), tela.OnDone(done))
    return Div(Role("status"), Class("rounded-lg bg-slate-900 px-4 py-2 text-white"),
        Style(tela.OpacityFn(func() float32 { return 1 - out.Get() })), Text("Saved"))
}
page := func() tela.Node {
    shown, setShown := tela.UseState(true)
    return Div(
        Button(OnClick(func(tela.MouseEvent) { setShown.Set(false) }), Text("Dismiss")),
        Presence(If(shown, tela.C(toast))))
}
app := telatest.Mount(t, tela.C(page))
start := app.Now()
app.Click(telatest.ByRole("button", "Dismiss")) // waits for the exit
fmt.Println(app.Count(telatest.ByRole("status", "")), app.Now()-start >= 200*time.Millisecond)
// Output: 0 true

func UseReducedMotion

func UseReducedMotion() bool

UseReducedMotion reports whether the system asks for reduced motion (the motion-reduce: classes apply, transitions take no time and scrolling jumps). The component builds again when it changes. It is a hook: call it at the top level of a component.

Example

spinner := func() tela.Node {
    if tela.UseReducedMotion() {
        return P(Text("Loading…"))
    }
    return Div(Class("size-6 animate-spin rounded-full border-2 border-t-transparent"), AriaLabel("Loading"), Role("status"))
}
yaml, _ := telatest.AriaSnapshot(tela.C(spinner), telatest.ReducedMotion())
fmt.Println(yaml)
// Output:
// - paragraph: Loading…

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")

It exists for code that wants React's shape, a setter that is a function: to pass it where a func(T) is expected (a callback of a library, a field's handler) without wrapping it, and to keep code ported from React reading the same. The function is the same on every build, but funcs are not comparable in Go, so a props struct holding one makes CP rebuild its component every time; pass the Setter of UseState in props instead.

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, for the same reasons as UseStateSet.

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

func UseTextScale

func UseTextScale() float64

UseTextScale returns how much the user asked the system to scale text (Windows' "Text size", GNOME's text scaling factor): 1.25 for text 25% larger. Tela applies it like a browser's default font size: 1rem is the theme's rem (16 px) times the scale, so the text-* sizes and the rem-based spacing of Tailwind (p-4, gap-2, w-64, the breakpoints) grow together while px values (border, text-[13px], w-[320px]) do not. It is 1 when the system reports nothing, when App.IgnoreTextScale is set, and in tests and snapshots. The component builds again when it changes. It is a hook: call it at the top level of a component.

Classes do not need it; it is for sizes computed in code.

func UseTimeout

func UseTimeout(d time.Duration, f func(), deps ...any)

UseTimeout calls f once on the UI thread, d after the frame that first shows the component: the one-shot sibling of UseInterval, on the same frame clock with no goroutine (telatest's App.Advance moves it, and a headless Render never runs it). An idle timeout costs one timer, and none once it has fired.

d <= 0 cancels it. A change of d or of the dependencies (compared like UseEffect's) restarts it from the frame that shows the change, even after it fired; with the same d and no dependencies it fires once per mount. f is always the latest build's function, so it sees the latest state without being a dependency. Unmounting cancels it.

Hide a notice 3 s after it appears, restarting when the message changes:

tela.UseTimeout(3*time.Second, func() { setVisible.Set(false) }, message)

Example

t := &exampleT{}
defer t.close()
banner := func() tela.Node {
    visible, setVisible := tela.UseState(true)
    // Hide the notice 3 s after it appears.
    tela.UseTimeout(3*time.Second, func() { setVisible.Set(false) })
    return Div(If(visible, P(Text("Saved"))), P(Text("Done")))
}
app := telatest.Mount(t, tela.C(banner))
app.Advance(2 * time.Second)
fmt.Println(app.Count(telatest.ByText("Saved")))
app.Advance(2 * time.Second) // the timeout runs on the app's clock
fmt.Println(app.Count(telatest.ByText("Saved")))
// Output:
// 1
// 0

func UseUpdateEffect

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

UseUpdateEffect is UseEffect without the run of the first build: the effect runs after the later builds whose dependencies changed (react-use's useUpdateEffect), and its cleanup before it runs again and on unmount. Use it to react to changes, not to the initial value.

tela.UseUpdateEffect(func() func() {
    save(settings) // not on mount, only when settings change
    return nil
}, settings)

Example

t := &exampleT{}
defer t.close()
var saved []string
editor := func() tela.Node {
    theme, setTheme := tela.UseState("light")
    tela.UseUpdateEffect(func() func() {
        saved = append(saved, theme) // not on mount: only changes are saved
        return nil
    }, theme)
    return Button(OnClick(func(tela.MouseEvent) { setTheme.Set("dark") }), Text("Dark"))
}
app := telatest.Mount(t, tela.C(editor))
fmt.Println(saved)
app.Click(telatest.ByRole("button", "Dark"))
fmt.Println(saved)
// Output:
// []
// [dark]

Types

type Animatable

type Animatable = core.Animatable

Animatable lists the types that UseSpring, UseTween and Animate animate:

  • float32: a number (an offset, a scale, an opacity, a progress);
  • Color: interpolated as CSS transitions interpolate colors, premultiplied by alpha, in sRGB between two colors written as hex, rgb() or named colors, and in Oklab otherwise (the Oklch palette of Tailwind, OKLCH); read it in a BgFn or TextColorFn to animate a color by repainting only;
  • Point, Size and Rect: component by component, each with its own velocity.

Animatable is an alias of core.Animatable, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Animatable interface {
    float32 | gfx.Color | Point | Size | Rect
}

Example

t := &exampleT{}
defer t.close()
var bg *tela.Value[tela.Color]
app := telatest.Mount(t, tela.C(func() tela.Node {
    bg = tela.UseValue(tela.Hex("#ffffff"))
    // Read in the paint phase: the animation only repaints the box.
    return Div(Class("size-10"), Style(tela.BgFn(bg.Get)))
}), telatest.NoSettle())
// Hex colors interpolate in sRGB, like a CSS transition between them.
tela.Animate(bg, tela.Hex("#000000"), tela.Duration(100*time.Millisecond), tela.Ease("linear"))
app.Advance(50 * time.Millisecond) // 33 ms of the tween: see ExamplePoint
fmt.Println(bg.Get())
// Output: #aaaaaa

type AnimationOption

type AnimationOption = core.AnimOption

AnimationOption configures an animation of UseSpring, UseTween or Animate: Stiffness, Damping, Bounce, Duration, Ease, Velocity and Spring, which groups spring options, OnDone, and Initial for the start value of UseSpring and UseTween. The zero AnimationOption changes nothing.

Springs (Motion's model) are defined either by their physics or by how long they take:

tela.Stiffness(300), tela.Damping(30)        // physics; mass 1
tela.Spring(tela.Duration(500*time.Millisecond), tela.Bounce(0.25))

Without options a spring has stiffness 100 and damping 10 (Motion's default, slightly bouncy); Bounce without a Duration takes 800 ms, and a Duration without Bounce a bounce of 0.3. Tweens take a Duration (default 300 ms) and an Ease (default CSS's "ease").

Options that contradict each other or the hook (Ease on a spring, Stiffness and Duration together, spring options on UseTween), invalid numbers and invalid easing strings panic in dev mode (see App.Dev) with what to write instead; otherwise they are ignored.

AnimationOption is an alias of core.AnimOption, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type AnimOption struct {
    // contains filtered or unexported fields
}

Example

// Options for the three kinds of motion.
physics := []tela.AnimationOption{tela.Stiffness(300), tela.Damping(30)}
timed := []tela.AnimationOption{tela.Spring(tela.Duration(400*time.Millisecond), tela.Bounce(0.2))}
tween := []tela.AnimationOption{tela.Duration(250 * time.Millisecond), tela.Ease("cubic-bezier(0, 0, 0.2, 1)")}
fmt.Println(len(physics), len(timed), len(tween))
// Output: 2 1 2

func Bounce

func Bounce(b float64) AnimationOption

Bounce sets how bouncy a spring defined by its duration is (Motion's bounce): 0 does not overshoot, 0.25 bounces a little, values toward 1 oscillate longer. Without a Duration the spring takes 800 ms. It makes Animate use a spring.

Example

// A spring defined by its duration (800 ms without Duration) ends no
// earlier than it, at rest.
fmt.Println("bounce 0:", springEnd(tela.Spring(tela.Bounce(0))))
fmt.Println("bounce 0.5:", springEnd(tela.Spring(tela.Bounce(0.5))))
// Output:
// bounce 0: 830ms
// bounce 0.5: 870ms

func Damping

func Damping(c float64) AnimationOption

Damping sets the damping of a spring: how strongly it resists motion (default 10). Low values bounce around the target; damping = 2*sqrt(stiffness) is critically damped (no overshoot), so Stiffness(300) with Damping(35) settles without bouncing. It makes Animate use a spring.

Example

// Damping 2*sqrt(stiffness) is critically damped: no overshoot. Less
// bounces around the target and takes longer to come to rest.
fmt.Println("damping 40:", springEnd(tela.Stiffness(400), tela.Damping(40)))
fmt.Println("damping 10:", springEnd(tela.Stiffness(400), tela.Damping(10)))
// Output:
// damping 40: 480ms
// damping 10: 1.15s

func Duration

func Duration(d time.Duration) AnimationOption

Duration sets how long a tween takes, or, for a spring (UseSpring, or inside Spring), the time a spring found from it settles in (Motion's duration/bounce springs; it ends no earlier). A Duration of 0 jumps to the target: the value is stored at once, a tween's or a spring's, and an OnDone is queued right after, as for any target that applies at once.

Example

fmt.Println("tween:", springEnd(tela.Duration(250*time.Millisecond)))
fmt.Println("spring:", springEnd(tela.Spring(tela.Duration(250*time.Millisecond))))
// Output:
// tween: 270ms
// spring: 320ms

func Ease

func Ease(e Easing) AnimationOption

Ease sets the easing curve of a tween, an Easing: a CSS <easing-function>, one of the keywords linear, ease, ease-in, ease-out, ease-in-out, step-start and step-end, CubicBezier or steps(n[, position]). The keywords are CSS's curves: Tailwind's ease-out utility is Ease(CubicBezier(0, 0, 0.2, 1)). An invalid string panics in dev mode with a suggestion (Ease("ease-out-cubic"): did you mean "ease-out"?); otherwise the tween uses "ease".

Example

fmt.Println("ease-out:", springEnd(tela.Ease("ease-out"))) // 300 ms by default
fmt.Println("steps:", springEnd(tela.Duration(time.Second), tela.Ease("steps(4, jump-end)")))
// Output:
// ease-out: 320ms
// steps: 1.03s

func Initial

func Initial[T Animatable](v T) AnimationOption

Initial sets the value UseSpring and UseTween hold on their first build, from which they move to their target: an entrance animation. Without it they start at the target, and nothing moves until the target changes. Later builds ignore it.

scale := tela.UseSpring(float32(1), tela.Initial(float32(0.8))) // pops in

Its type must be the target's. AnimationOption is not generic, so the compiler cannot check that: a mismatch, such as tela.UseSpring(tela.Point{}, tela.Initial(float32(0))), panics in dev mode (see App.Dev) and is ignored otherwise (the value starts at the target). It does not apply to Animate, which starts where the Value is (dev mode panics), nor inside Spring.

func OnDone

func OnDone(fn func()) AnimationOption

OnDone calls fn when the animation comes to rest at its target: once, on the UI thread, queued with the state updates like Post, so it runs before the next build and state it sets is built in the frame that shows the target. It is not called when the animation is interrupted: by another Animate, Set or Update of the Value, by a new target of UseSpring or UseTween, or by the unmount of the Value's component. When the target applies at once (reduced motion, a tree that no frame clock drives, Duration(0), a value already at its target and at rest, a NaN target) fn is queued right after it applies. For UseSpring and UseTween it is the OnDone of the build that set the target that runs.

tela.Animate(x, 0, tela.Spring(), tela.OnDone(func() { setOpen.Set(false) }))

It is how a component inside an h.Presence child ends its own exit: see UsePresence.

Example

t := &exampleT{}
defer t.close()
var x *tela.Value[float32]
var log []string
app := telatest.Mount(t, tela.C(func() tela.Node {
    x = tela.UseValue[float32](0)
    return Div(Class("size-8 bg-black"), Style(tela.TranslateFn(func() (float32, float32) { return x.Get(), 0 })))
}), telatest.NoSettle())
tela.Animate(x, 100, tela.OnDone(func() { log = append(log, "first") }))
app.Advance(50 * time.Millisecond)
// Interrupted: the first OnDone never runs.
tela.Animate(x, 0, tela.Duration(100*time.Millisecond), tela.OnDone(func() { log = append(log, "second") }))
app.Advance(time.Second)
fmt.Println(log, x.Get())
// Output: [second] 0

func Spring

func Spring(opts ...AnimationOption) AnimationOption

Spring makes the animation a spring with the options given: Stiffness and Damping, or Duration and Bounce. Spring() alone is Motion's default spring. It is how Animate gets a spring defined by its duration:

tela.Animate(y, 200, tela.Spring(tela.Duration(400*time.Millisecond), tela.Bounce(0.3)))

Example

fmt.Println("Motion's default:", springEnd(tela.Spring()))
fmt.Println("500 ms, bounce 0.3:", springEnd(tela.Spring(tela.Duration(500*time.Millisecond), tela.Bounce(0.3))))
// Output:
// Motion's default: 1.07s
// 500 ms, bounce 0.3: 530ms

func Stiffness

func Stiffness(k float64) AnimationOption

Stiffness sets the stiffness of a spring: how strongly it pulls the value to its target (default 100). Higher is faster. With Damping it defines the spring by its physics, so it cannot be combined with Duration or Bounce. It makes Animate use a spring.

Example

// Four times the stiffness (with the damping that keeps both critically
// damped) arrives in about half the time.
fmt.Println("stiffness 100:", springEnd(tela.Stiffness(100), tela.Damping(20)))
fmt.Println("stiffness 400:", springEnd(tela.Stiffness(400), tela.Damping(40)))
// Output:
// stiffness 100: 870ms
// stiffness 400: 480ms

func Velocity

func Velocity[T Animatable](v T) AnimationOption

Velocity sets the initial velocity of a spring, in the Value's units per second, component by component (a Point's X and Y, a Rect's X, Y, Width and Height). It replaces the velocity an interrupted animation would pass on: a released drag hands its pointer velocity to the spring that brings the element back:

OnDragEnd(func(e tela.DragEvent) {
    tela.Animate(pos, tela.Point{}, tela.Spring(), tela.Velocity(tela.Point{X: e.VelocityX, Y: e.VelocityY}))
})

It makes Animate use a spring. Its type must be the Value's, and it does not apply to tweens (UseTween, or Duration and Ease alone) nor to Color values, whose components move in a space (sRGB or Oklab, premultiplied) that depends on the two colors: those panic in dev mode and are ignored otherwise.

Example

t := &exampleT{}
defer t.close()
var x *tela.Value[float32]
app := telatest.Mount(t, tela.C(func() tela.Node {
    x = tela.UseValue[float32](0)
    return Div(Class("size-8 bg-black"), Style(tela.TranslateFn(func() (float32, float32) { return x.Get(), 0 })))
}), telatest.NoSettle())
// A flick at rest: the value is already at its target, the velocity
// carries it away and the spring brings it back.
tela.Animate(x, 0, tela.Stiffness(200), tela.Damping(20), tela.Velocity(float32(600)))
var peak float32
for range 30 {
    app.Advance(time.Second / 60)
    peak = max(peak, x.Get())
}
app.Advance(2 * time.Second)
fmt.Printf("peak %.0f, rest %v\n", peak, x.Get())
// Output: peak 19, rest 0

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; set both or
    // neither.
    Width, Height float32
    // MinWidth, MinHeight, MaxWidth and MaxHeight limit the size the user
    // can give the window; zero is no limit on that side.
    MinWidth, MinHeight, MaxWidth, MaxHeight float32
    // FixedSize keeps the user from resizing the window.
    FixedSize bool
    // Frameless removes the OS title bar and borders: the app draws its
    // own title bar, marked with h.WindowDrag and h.WindowControl. The
    // window stays resizable at its edges (unless FixedSize).
    Frameless bool
    // AlwaysOnTop keeps the window above other windows (not on Wayland).
    AlwaysOnTop bool
    // Hidden starts the window hidden (an app that starts in the tray):
    // [WindowHandle].Show shows it.
    Hidden bool
    // Icon is the window's icon (title bar, task bar, alt-tab), the same
    // picture at several sizes (16 to 256 pixels; the OS picks). Empty is
    // the default icon (on Linux, the one of the .desktop file of AppID).
    Icon []image.Image
    // TitleBar sets the colors of the native title bar and border on
    // Windows 11; see [TitleBarColors]. The zero value keeps the
    // system's. Other platforms ignore it.
    TitleBar TitleBarColors
    // Backdrop is the system material behind the window (Mica, Mica Alt,
    // Acrylic) on Windows 11 22H2; see [Backdrop]. The zero value is the
    // system's choice. Other platforms ignore it.
    Backdrop Backdrop
    // Placement is where the window opens: a WindowPlacement saved from
    // [WindowHandle].Placement in an earlier run, so the window comes back
    // where the user left it. Its size, when set, wins over Width and
    // Height. The zero value lets the OS place the window.
    Placement WindowPlacement
    // OnClose runs when the user asks to close the main window (its close
    // button, Alt+F4, a WindowClose control, [WindowHandle].Close). Nil,
    // the default, quits the app. To keep running in the tray, hide the
    // window instead; [Quit] ends the app later:
    //
    //	OnClose: func(w tela.WindowHandle) { w.Hide() },
    OnClose func(WindowHandle)
    // 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: a struct component value (Root: Notes{})
    // or a function component made with [C] (Root: Notes(), where Notes
    // returns tela.C(notes)). It is a
    // component of its own, so it may use hooks. A [Node] works too, but a
    // root that uses hooks must be a component.
    Root Component
    // 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
    // NativeMenus makes kit's ContextMenu and Menubar use the OS's menus
    // where the platform has them, instead of drawing their own (ADR-215).
    // It is the app's default for every menu, and every child
    // [Window] follows it; a menu's own Native setting (kit.NativeOn,
    // kit.NativeOff) wins over it, so a menu can stay drawn while the
    // others are native. Windows has native menus now (context menus, and
    // menu bars on windows with a title bar of the OS), macOS will with
    // its desktop integration; Linux never does, and a frameless Windows window has no
    // menu bar: there the menus are drawn as if this were false.
    //
    // What a native menu cannot show (icons, descriptions, custom content,
    // the destructive color, keeping the menu open) is dropped; see
    // kit.ContextMenu and kit.Menubar. Off, the default, keeps one look on
    // every platform. Components read the setting with [UseNativeMenus].
    NativeMenus bool
    // IgnoreTextScale keeps 1rem at the theme's 16 px whatever text size
    // the user chose in the system's settings. By default Tela follows
    // it like a browser's default font size: 1rem grows with the
    // system's text scale, so text and rem-based spacing grow together
    // (see [UseTextScale]). Set it for a layout that cannot grow, such as
    // a fixed-size tool window.
    IgnoreTextScale bool
    // ColorScheme is the app's color scheme: Light or Dark override the
    // system's preference (the dark: classes, UseColorScheme); the zero
    // value, System, follows it. SetColorScheme changes it while the app
    // runs.
    ColorScheme ColorScheme
    // Theme holds the color tokens (bg-primary, text-muted-foreground),
    // the base radius and the fonts of the app; nil means DefaultTheme,
    // so the shadcn/ui tokens always resolve. SetTheme changes it while
    // the app runs. See [Theme].
    Theme *Theme
    // ClassAliases are class names that stand for class strings, for the
    // combinations an app repeats:
    //
    //	ClassAliases: map[string]string{
    //	    "text-muted": "text-sm text-muted-foreground",
    //	    "card-title": "text-lg font-semibold dark:text-white",
    //	}
    //
    // An alias expands where it appears, so later classes still win over
    // its classes and its classes over earlier ones. Its variants apply
    // to every class of the expansion: "hover:card-title" is "hover:text-lg
    // hover:font-semibold hover:dark:text-white". An alias may use other
    // aliases; a cycle is reported like an unknown class. An alias wins
    // over a class of the same name ("text-muted" is also the muted token
    // as a text color). A key is one class name without variants, spaces
    // or brackets; Run panics otherwise. Aliases are also suggested for
    // misspelled classes.
    ClassAliases map[string]string
    // Dev turns dev mode on or off. Dev mode reports the mistakes that
    // compile: unknown classes are printed on standard error; unknown
    // roles, input types and easings panic with the closest valid name;
    // hooks called in another order or outside their component panic
    // with the fix; likely mistakes (lists without keys, nil
    // dependencies, a Value read while changes are queued...) are printed
    // as warnings. It costs some speed.
    //
    // Nil, the default, means on unless the program is a release build:
    // go run and go build in a source checkout give development builds,
    // while a binary built with -trimpath, or installed with go install
    // pkg@version, is a release build. The environment variable
    // TELA_DEV=1 or TELA_DEV=0 overrides both Dev and the default.
    //
    //	off := false
    //	tela.Main(tela.App{Title: "Notes", Root: Notes(), Dev: &off})
    Dev *bool
    // DevState keeps state that lives outside the component tree across
    // the reloads of tela dev: package variables, controllers, the
    // settings of a connection. State in hooks, text fields, scroll
    // offsets and focus is kept without it. See [DevState]; the field is
    // ignored outside tela dev.
    DevState DevState
}

App describes an application window for Run.

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

type Backdrop

type Backdrop = core.Backdrop

Backdrop is the system material behind a window (App.Backdrop, Window.Backdrop): Mica, Mica Alt or Acrylic on Windows 11.

The zero value, BackdropDefault, leaves the choice to the system. On Windows 11 22H2 (build 22621 and later) the other values apply the material to the native title bar of a decorated window, always; and, for BackdropMica, BackdropMicaAlt and BackdropAcrylic, to the content of the window where the app leaves it transparent. The root has no background by default (see RootClass), so a root without a background class shows the material, a root with a translucent one tints it, and the surfaces the app draws over it (a sidebar with bg-white/60, a card with bg-card) cover it. A root with bg-background hides it:

tela.App{
    Backdrop:  tela.BackdropMica,
    RootClass: "bg-background/60", // Mica shows through; elsewhere a plain background
    Root:      Notes{},
}

The material follows the dark or light title bar, which follows the app's color scheme. A frameless window with a client-area backdrop extends the window frame over its content (DwmExtendFrameIntoClientArea), which Windows 11 may answer by no longer showing the snap layouts flyout over the app's maximize button; keep a frameless window without a backdrop if that matters.

Windows 10, Linux and headless ignore Backdrop, and so does Windows 11 before 22H2: the window stays as without it, so an app that leaves its root transparent should still give it a background color for them (a dark: class pair, or a background only where the backdrop is missing). Changing the field of an open Window updates the window.

Backdrop is an alias of core.Backdrop, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Backdrop = platform.Backdrop

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. The zero Callback is tied to no slot: Fn panics, and IsZero reports it, for optional callbacks in props:

if !p.OnClose.IsZero() {
    p.OnClose.Fn()()
}

Callback is an alias of core.Callback, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Callback[F any] struct {
    // contains filtered or unexported fields
}

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 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.

func (Callback) Fn

func (c Callback[F]) Fn() F

Fn returns the slot's forwarding function: one function of type F, the same on every build, that calls the latest function given to UseCallback at the time it is called. Call it on the UI thread (event handlers and effects run there); from other goroutines, use Post.

func (Callback) IsZero

func (c Callback[F]) IsZero() bool

IsZero reports whether c is the zero Callback, which is not tied to any UseCallback slot: Fn panics. A Callback left unset in a props struct, or read from a context default without a Provider, is zero.

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))

ChangeEvent is an alias of core.ChangeEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type ChangeEvent struct {
    Value       string
    InputType   string
    Data        string
    IsComposing bool
    // contains filtered or unexported fields
}

func (ChangeEvent) DefaultPrevented

func (e ChangeEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (ChangeEvent) PreventDefault

func (e ChangeEvent) PreventDefault()

PreventDefault marks the event; the edit already happened.

func (ChangeEvent) StopPropagation

func (e ChangeEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

type Child

type Child = core.Child

Child is what an element takes: a Node (a child, an element or a struct component, which becomes a component with its own state) or a Prop (h.Class, h.Style, h.Key, h.OnClick...). Props and children may be mixed in any order. It has the method set of Node but is a type of its own: a field or parameter of type Child takes props too, one of type Node (or Component) is for what renders. A Child is assignable to a Node, so a function that returns a Node may return h.If(...). Passing a string or a number is a compile error; wrap text in h.Text. The compiler names the internal type behind the alias:

cannot use "hello" (constant of type string) as tela.Child value in
argument to Div: string does not implement core.Child (missing method
Build)

means Div("hello") should be Div(Text("hello")).

Child is an alias of core.Child, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Child interface {
    Build() Node
}

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.

Color is an alias of gfx.Color, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Color struct{ R, G, B, A float32 }

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

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).

func UseAccentColor

func UseAccentColor() (Color, bool)

UseAccentColor returns the user's accent color (the color the system uses for selections and focused controls), and false when the system has none or does not report it. The component builds again when it changes. It is a hook: call it at the top level of a component.

accent, ok := tela.UseAccentColor()
if !ok {
    accent = tela.Hex("#4f46e5")
}
return Div(Style(tela.Bg(accent)), ...)

func (Color) Clamp

func (c Color) Clamp() Color

Clamp returns c with every component clamped to [0, 1]. NaN becomes 0. For an extended sRGB color this per-channel clip is what browsers paint.

func (Color) IsFinite

func (c Color) IsFinite() bool

IsFinite reports whether all four components are finite.

func (Color) IsOpaque

func (c Color) IsOpaque() bool

IsOpaque reports whether A >= 1.

func (Color) MulAlpha

func (c Color) MulAlpha(f float32) Color

MulAlpha returns c with its alpha multiplied by f (Tailwind's "bg-white/15" is White.MulAlpha(0.15)).

func (Color) NRGBA8

func (c Color) NRGBA8() color.NRGBA

NRGBA8 returns the non-premultiplied 8-bit color, rounded to nearest.

func (Color) Premul8

func (c Color) Premul8() color.RGBA

Premul8 returns the premultiplied 8-bit color, with every channel rounded to nearest (half up) from the exact float value, never by premultiplying already-rounded bytes. The result satisfies R,G,B <= A.

func (Color) Premultiplied

func (c Color) Premultiplied() (r, g, b, a float32)

Premultiplied returns the premultiplied components (R*A, G*A, B*A, A) as floats in [0, 1].

func (Color) String

func (c Color) String() string

String returns "#rrggbb" for opaque colors and "#rrggbbaa" otherwise, with components rounded to 8 bits.

func (Color) ToOKLCH

func (c Color) ToOKLCH() (l, ch, h float32)

ToOKLCH returns the OKLCH coordinates of c (alpha is ignored). The hue h is in degrees in [0, 360); it is 0 for achromatic colors.

func (Color) ToOklab

func (c Color) ToOklab() (l, a, b float32)

ToOklab returns the Oklab coordinates of c (alpha is ignored), extended sRGB components included. l is in [0, 1] for in-gamut colors; a and b are roughly in [-0.4, 0.4].

func (Color) WithAlpha

func (c Color) WithAlpha(a float32) Color

WithAlpha returns c with its alpha replaced by a.

type ColorScheme

type ColorScheme = core.ColorScheme

ColorScheme is a color scheme: what the app asks for (System, Light or Dark) in App.ColorScheme and SetColorScheme, and the effective scheme UseColorScheme returns (Light or Dark). The dark: classes apply in the effective Dark scheme.

ColorScheme is an alias of core.ColorScheme, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type ColorScheme uint8
const (
    // ColorSchemeSystem follows the system's preference (the zero
    // value).
    ColorSchemeSystem ColorScheme = iota
    // ColorSchemeLight is the light scheme: dark: variants do not apply.
    ColorSchemeLight
    // ColorSchemeDark is the dark scheme: dark: variants apply.
    ColorSchemeDark
)
const (
    // System follows the system's light or dark preference. It is the
    // zero value, so an [App] follows the system unless it sets
    // ColorScheme.
    System ColorScheme = core.ColorSchemeSystem
    // Light is the light scheme: dark: classes do not apply.
    Light ColorScheme = core.ColorSchemeLight
    // Dark is the dark scheme: dark: classes apply.
    Dark ColorScheme = core.ColorSchemeDark
)

func UseColorScheme

func UseColorScheme() ColorScheme

UseColorScheme returns the color scheme at the component's position, Light or Dark: the one the nearest element above it with an h.Scheme prop gives, else the app's, the one the app set (App.ColorScheme, SetColorScheme) or, with System, the system's preference. The component builds again when it changes, like with a state hook. It is a hook: call it at the top level of a component.

func Logo() tela.Node {
    src := lightLogo
    if tela.UseColorScheme() == tela.Dark {
        src = darkLogo
    }
    return Img(SrcImage(src), Alt("Tela"))
}

Classes do not need it: dark:bg-slate-900 already follows the scheme.

Example

theme := func() tela.Node {
    name := "light"
    if tela.UseColorScheme() == tela.Dark {
        name = "dark"
    }
    return P(Textf("Theme: %s", name))
}
// The system prefers dark (telatest.Dark), the app follows it.
yaml, _ := telatest.AriaSnapshot(tela.C(theme), telatest.Dark())
fmt.Println(yaml)
// The app overrides the system (tela.App.ColorScheme).
yaml, _ = telatest.AriaSnapshot(tela.C(theme), telatest.Dark(), telatest.ColorScheme(tela.Light))
fmt.Println(yaml)
// Output:
// - paragraph: "Theme: dark"
// - paragraph: "Theme: light"

func (ColorScheme) String

func (s ColorScheme) String() string

type Component

type Component = Node

Component is Node: the name for a field or a parameter that holds what renders, such as the Children of a struct component, or the root of App, Preview and Window. Both names are the same type.

Example

t := &exampleT{}
defer t.close()
app := telatest.Mount(t, Div(Class("flex flex-col gap-2"),
    Panel{Title: "Inbox", Children: Fragment(P(Text("2 new")), P(Text("from Ada")))},
    Panel{Title: "Archive", Children: P(Text("empty"))}))
// Each Panel has its own state: this closes the first one only.
app.Click(telatest.ByRole("button", "Inbox"))
fmt.Println(app.AriaSnapshot())
// Output:
// - button "Inbox"
// - button "Archive" [expanded]
// - paragraph: empty

Example (skipping)

t := &exampleT{}
defer t.close()
counts := map[string]int{}
builds := &counts
page := func() tela.Node {
    clicks, setClicks := tela.UseState(0)
    return Div(
        Button(OnClick(func(tela.MouseEvent) { setClicks.Set(clicks + 1) }), Text("Click")),
        Badge{Label: "Inbox", Count: 3, builds: builds},       // equal on every build: skipped
        Badge{Label: "Clicks", Count: clicks, builds: builds}, // changes: built again
    )
}
app := telatest.Mount(t, tela.C(page))
app.Click(telatest.ByRole("button", "Click"))
app.Click(telatest.ByRole("button", "Click"))
fmt.Println(counts["Inbox"], counts["Clicks"])
// Output: 1 3

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.

CompositionEvent is an alias of core.CompositionEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type CompositionEvent struct {
    Data string
    // contains filtered or unexported fields
}

func (CompositionEvent) DefaultPrevented

func (e CompositionEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (CompositionEvent) PreventDefault

func (e CompositionEvent) PreventDefault()

PreventDefault marks the event; compositions cannot be canceled.

func (CompositionEvent) StopPropagation

func (e CompositionEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

type Context

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

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

Context is an alias of core.Context, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Context[T any] struct {
    // contains filtered or unexported fields
}

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, 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.

func (*Context) Provider

func (c *Context[T]) Provider(value T, children ...Child) Node

Provider returns a node that gives value to the UseContext calls of its children's subtrees. It has no box: the children become children of the provider's parent, like a Fragment, and only Key is accepted as a prop.

type DataTransfer

type DataTransfer = core.DataTransfer

DataTransfer is the data of a drag and drop (HTML's dataTransfer): the same content in one or more representations, each under a MIME type, best first. Read it with Text, HTML, Files, Image, GetData and Get; fill it, in the OnDragStart of an h.Draggable element, with SetText, SetHTML, SetFiles, SetImage, SetData and Set. Before the drop a target sees only the types (Types, Has); the data arrives with OnDrop. DropEffect and EffectAllowed are HTML's dropEffect and effectAllowed.

Div(Draggable(true), Class("cursor-grab"),
    OnDragStart(func(e tela.DragEvent) {
        e.DataTransfer.SetData("application/x-board-card", card.ID)
        e.DataTransfer.SetText(card.Title) // for other applications
        e.DataTransfer.EffectAllowed = tela.DropMove
    }),
    OnDragEnd(func(e tela.DragEvent) {
        if e.DataTransfer.DropEffect == tela.DropMove {
            remove(card.ID)
        }
    }),
    Text(card.Title),
)

The zero value is an empty DataTransfer that the setters fill.

DataTransfer is an alias of core.DataTransfer, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type DataTransfer struct {
    // DropEffect is the effect of the drop (HTML's dropEffect). Before
    // OnDragEnter and OnDragOver it holds the effect the platform's
    // convention picks from EffectAllowed and the modifiers held (Ctrl
    // copies, Shift moves, Ctrl+Shift links, else copy); a handler that
    // accepts the drop (PreventDefault in OnDragOver) may change it. In
    // OnDrop it is the accepted effect: setting DropNone tells the source
    // that the data was not taken (a moving source keeps it). In the
    // source's OnDragEnd it is the effect performed, DropNone for a drag
    // that was cancelled or refused.
    DropEffect DropEffect
    // EffectAllowed is the set of effects the source allows (HTML's
    // effectAllowed): OnDragStart starts it at DropAll and may narrow it
    // (tela.DropCopy|tela.DropMove); the target's events report it.
    EffectAllowed DropEffect
    // contains filtered or unexported fields
}

func (*DataTransfer) ClearData

func (d *DataTransfer) ClearData(formats ...string)

ClearData removes the representations of the formats given (see Has for the names), or all of them when none is given.

func (*DataTransfer) Files

func (d *DataTransfer) Files() []string

Files returns the absolute paths of the files, in order: the local file: URIs of the URI list (other URIs, such as a link dragged from a browser, are left out; GetData("url") returns the first URI).

func (*DataTransfer) Get

func (d *DataTransfer) Get(mime string) ([]byte, bool)

Get returns the bytes of the representation of a MIME type (see Has for the names), and whether there is one. Before the drop it returns nothing. The caller must not modify the bytes.

func (*DataTransfer) GetData

func (d *DataTransfer) GetData(format string) string

GetData returns the representation of a format as a string, like HTML's getData: "text" or "text/plain" is the text, "url" the first URI of the URI list, any other MIME type its bytes. It is "" when there is none, and before the drop.

func (*DataTransfer) HTML

func (d *DataTransfer) HTML() string

HTML returns the HTML fragment, "" when there is none.

func (*DataTransfer) Has

func (d *DataTransfer) Has(mime string) bool

Has reports whether the data has a representation of the MIME type: "text/plain" (or "text"), "text/html", "image/png", "text/uri-list" (or "Files", "url"), or a custom type. It works before the drop too, which is how OnDragOver decides whether to accept a drag:

OnDragOver(func(e tela.DragEvent) {
    if e.DataTransfer.Has("Files") {
        e.PreventDefault() // accept the drop
    }
})

A custom type a handler asks about before the drop is read at the drop, besides the well-known types.

func (*DataTransfer) Image

func (d *DataTransfer) Image() (img image.Image, ok bool)

Image returns the PNG image, decoded; ok is false when there is none or it does not decode.

func (*DataTransfer) Set

func (d *DataTransfer) Set(mime string, b []byte)

Set puts the bytes of a MIME type in the data, replacing the bytes of that type, else after the other types (the order is best first). The names of Has work ("text/plain" is MIMEText). b is used as is: do not modify it afterwards. A type that is not a MIME type panics.

func (*DataTransfer) SetData

func (d *DataTransfer) SetData(format, data string)

SetData puts a string under a format, like HTML's setData: "text" or "text/plain" is the text, "url" a URI list of one URI, any other MIME type takes the string's bytes.

OnDragStart(func(e tela.DragEvent) {
    e.DataTransfer.SetData("application/x-kanban-card", card.ID)
    e.DataTransfer.SetText(card.Title) // for other applications
    e.DataTransfer.EffectAllowed = tela.DropMove
})

func (*DataTransfer) SetDragImage

func (d *DataTransfer) SetDragImage(img image.Image, x, y int)

SetDragImage sets the picture shown under the pointer during the drag (HTML's setDragImage), in place of the element's own: img in device pixels, and (x, y) the point of img under the pointer. A nil img shows no picture, only the platform's cursor. Without SetDragImage the drag shows the element as it is on screen. Call it in OnDragStart; an image larger than 1024 pixels on a side is cut to the 1024 around the point.

func (*DataTransfer) SetFiles

func (d *DataTransfer) SetFiles(paths ...string) error

SetFiles puts files in the data, as a URI list of their absolute paths: a file manager, or any application that takes files, receives them. It returns an error for an empty list or a path that is not absolute.

func (*DataTransfer) SetHTML

func (d *DataTransfer) SetHTML(s string)

SetHTML puts an HTML fragment in the data; add SetText for the applications that read only text.

func (*DataTransfer) SetImage

func (d *DataTransfer) SetImage(img image.Image) error

SetImage puts an image in the data, encoded as PNG. It returns the encoder's error.

func (*DataTransfer) SetText

func (d *DataTransfer) SetText(s string)

SetText puts plain text in the data.

func (*DataTransfer) Text

func (d *DataTransfer) Text() string

Text returns the plain text, "" when there is none.

func (*DataTransfer) Types

func (d *DataTransfer) Types() []string

Types returns the MIME types of the data, best first. Text is "text/plain;charset=utf-8" (MIMEText); compare with Has, which also takes "text/plain".

type DevState

type DevState struct {
    // Save returns the state to keep. Nil keeps nothing.
    Save func() ([]byte, error)
    // Restore receives what Save returned in the previous process. Nil
    // ignores it.
    Restore func([]byte) error
}

DevState is the state of an app that lives outside its component tree, to keep across the reloads of tela dev: package variables, a controller object, the settings of a connection. tela dev rebuilds the app on every save and runs the new build as a new process; the state of hooks, text fields, scroll offsets and focus is carried over by Tela itself (it is in the tree), and DevState carries the rest.

Before it stops the old process, tela dev calls Save and keeps the bytes (encode them with encoding/gob, JSON or anything else). In the new process it calls Restore with those bytes before the root mounts, so the first build already sees the restored values. A Save or Restore that returns an error or panics is reported as a warning and that state starts fresh; Restore is not called when Save returned no bytes, nor on the first start or after a restart with fresh state (the R key, --no-state). Both run on the UI thread, so they need no locking against the components.

Outside tela dev (tela.Main, tela snapshot, tests, release builds) DevState is ignored and neither function is called.

To keep the save and restore code, and encoding/gob, out of release builds, give the field a variable that only a file built with the teladev tag assigns:

// app.go
var devState tela.DevState

func main() {
    tela.Main(tela.App{Title: "Notes", Root: Notes{}, DevState: devState})
}

// devstate_dev.go
//go:build teladev

func init() {
    devState = tela.DevState{
        Save:    func() ([]byte, error) { return json.Marshal(current) },
        Restore: func(b []byte) error { return json.Unmarshal(b, &current) },
    }
}

tela dev builds with that tag; go build and go install do not.

Example

A variable outside the component tree survives a tela dev reload when the app saves and restores it. The example plays both processes: in a real app tela dev calls Save before it stops the old build and Restore before the new one mounts, and the functions live in a file built only with the teladev tag (see tela.DevState).

selected := "inbox" // lives in a package variable, not in a hook

devState := tela.DevState{
    Save:    func() ([]byte, error) { return json.Marshal(selected) },
    Restore: func(b []byte) error { return json.Unmarshal(b, &selected) },
}
_ = tela.App{Title: "Mail", DevState: devState}

saved, _ := devState.Save() // the old process, before it stops
selected = ""               // the new process starts with the zero value...
devState.Restore(saved)     // ...and gets the state back before it mounts
fmt.Println(selected)
// Output: inbox

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.

Dispatcher is an alias of core.Dispatcher, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Dispatcher[A any] struct {
    // contains filtered or unexported fields
}

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"})

func (Dispatcher) Dispatch

func (d Dispatcher[A]) Dispatch(a A)

Dispatch queues action a. When the update applies on the UI thread, the reducer of the latest build computes the new state from the state left by the previous queued update; for comparable states, a result == to the current state does not rebuild.

type DragEvent

type DragEvent = core.DragEvent

DragEvent is passed to the drag handlers of package h, for two kinds of drags.

The drag gesture (h.OnDragStart, h.OnDrag, h.OnDragEnd) moves something inside the app: 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). Its events go to one element and do not bubble; DataTransfer is nil.

Drag and drop, HTML's model, carries data to a drop target in the same window, another window of the app or another application (a file manager, an editor): an element with h.Draggable(true) fills e.DataTransfer in its OnDragStart, and its OnDragEnd reports what the target did (e.DataTransfer.DropEffect). Targets get h.OnDragEnter, h.OnDragOver, h.OnDragLeave and h.OnDrop, which bubble; a target accepts the drop by calling e.PreventDefault() in OnDragOver, as in HTML:

Div(Class("rounded-lg border-2 border-dashed p-8", over),
    OnDragEnter(func() { setOver.Set(true) }),
    OnDragLeave(func() { setOver.Set(false) }),
    OnDragOver(func(e tela.DragEvent) {
        if e.DataTransfer.Has("Files") {
            e.PreventDefault() // accept: the drop comes here
        }
    }),
    OnDrop(func(e tela.DragEvent) {
        setOver.Set(false)
        open(e.DataTransfer.Files())
    }),
    Text("Drop files here"),
)

OnDragEnter and OnDragLeave follow HTML: they fire when the element under the drag changes (enter on the new one, then leave on the old one), and they bubble, so a container also gets them as the drag moves between its children; a container that tracks "is the drag over me" counts them (enter adds one, leave removes one) or uses OnDragOver.

DragEvent is an alias of core.DragEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type DragEvent struct {
    // X and Y are the position in the window, LocalX and LocalY relative
    // to the border box of the element (see MouseEvent).
    X, Y, LocalX, LocalY float32
    // DeltaX and DeltaY are the movement since the previous drag event
    // (since the press for OnDragStart).
    DeltaX, DeltaY float32
    // OffsetX and OffsetY are the movement since the press: an element
    // moved by them follows the pointer exactly.
    OffsetX, OffsetY float32
    // VelocityX and VelocityY are the pointer velocity in logical pixels
    // per second, from the last 100 ms of moves; meaningful at
    // OnDragEnd (a fling), zero when the pointer stopped before the
    // release or the drag was cancelled.
    VelocityX, VelocityY float32
    // Button is the button of the drag (MouseButtonLeft); MouseButtonNone
    // for the events of a drop target.
    Button MouseButton
    // Modifier keys held during the event.
    Shift, Ctrl, Alt, Meta bool
    // DataTransfer is the data of a drag and drop (HTML's dataTransfer):
    // what the source's OnDragStart fills, what the targets read, and the
    // effects (DropEffect, EffectAllowed). It is nil in the drag gesture
    // of an element without Draggable(true).
    DataTransfer *DataTransfer
    // contains filtered or unexported fields
}

func (DragEvent) CmdOrCtrl

func (e DragEvent) CmdOrCtrl() bool

CmdOrCtrl reports whether the primary modifier of shortcuts is held (see MouseEvent.CmdOrCtrl).

func (DragEvent) DefaultPrevented

func (e DragEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (DragEvent) PreventDefault

func (e DragEvent) PreventDefault()

PreventDefault in OnDragOver accepts the drop (HTML's rule: a target that does not call it refuses the drag); in OnDragStart of a Draggable element it cancels the drag and drop (the press stays a drag gesture). Elsewhere it marks the event (see MouseEvent).

func (DragEvent) StopPropagation

func (e DragEvent) StopPropagation()

StopPropagation stops the bubbling of a drop target's event (OnDragEnter, OnDragOver, OnDragLeave, OnDrop); the gesture's events go to one element and do not bubble.

type DropEffect

type DropEffect = core.DropEffect

DropEffect is what a drop does with the data: DropCopy, DropMove (the source deletes its copy) or DropLink; DropNone refuses it. As a set, tela.DropCopy|tela.DropMove, it is the effects a source allows (DataTransfer.EffectAllowed).

DropEffect is an alias of core.DropEffect, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type DropEffect = platform.DropEffect

type Easing

type Easing string

Easing is a CSS <easing-function> as a string: one of the constants EaseLinear, EaseIn, EaseOut and EaseInOut, the keywords "ease", "step-start" and "step-end", CubicBezier, or "steps(n[, position])". It is a string type, so a literal such as "ease-out" keeps compiling where an Easing is wanted.

const (
    // EaseLinear is "linear": constant speed.
    EaseLinear Easing = "linear"
    // EaseIn is "ease-in", cubic-bezier(0.42, 0, 1, 1): starts slow.
    EaseIn Easing = "ease-in"
    // EaseOut is "ease-out", cubic-bezier(0, 0, 0.58, 1): ends slow.
    EaseOut Easing = "ease-out"
    // EaseInOut is "ease-in-out", cubic-bezier(0.42, 0, 0.58, 1): slow at
    // both ends.
    EaseInOut Easing = "ease-in-out"
)

The easing keywords of CSS Easing Functions Level 1 that Tailwind and Motion also name. They are CSS's curves: Tailwind's ease-out utility is CubicBezier(0, 0, 0.2, 1), which is not EaseOut. The default of a tween is CSS's "ease", which has no constant.

Example

// The constants and string literals are both Easings; a variable
// that holds a CSS easing converts with tela.Easing(s).
opts := []tela.AnimationOption{
    tela.Ease(tela.EaseInOut),
    tela.Ease("ease-out"),
    tela.Ease(tela.Easing("steps(4, jump-end)")),
}
fmt.Println(len(opts), tela.EaseLinear, tela.EaseIn, tela.EaseOut, tela.EaseInOut)
// Output: 3 linear ease-in ease-out ease-in-out

func CubicBezier

func CubicBezier(x1, y1, x2, y2 float32) Easing

CubicBezier returns the easing cubic-bezier(x1, y1, x2, y2): a Bezier curve from (0, 0) to (1, 1) with those control points. x1 and x2 must be from 0 to 1 (y1 and y2 may overshoot, for a bounce); Ease panics in dev mode otherwise.

tela.Ease(tela.CubicBezier(0, 0, 0.2, 1)) // Tailwind's ease-out

Example

// Tailwind's ease-out utility, and a curve that overshoots.
fmt.Println(tela.CubicBezier(0, 0, 0.2, 1))
fmt.Println(tela.CubicBezier(0.34, 1.56, 0.64, 1))
_ = tela.Ease(tela.CubicBezier(0, 0, 0.2, 1))
// Output:
// cubic-bezier(0, 0, 0.2, 1)
// cubic-bezier(0.34, 1.56, 0.64, 1)

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.
  • Click clicks the element like HTML's element.click(): a click event with the primary button, at the center of the element, is dispatched at it (OnClick handlers run and it bubbles; the hooks of package aria see a virtual press), then, unless a handler prevented it, the activation behavior runs: a Button in a Form submits it or resets it by its h.Type, a Label focuses its control and clicks it. No pointer events and no focus change. A disabled or unmounted element ignores it. Call it in a handler or an effect: the state it sets is applied and built by the event or the frame that is running.
  • 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; in a right-to-left element x is 0 at the right and negative toward the left). 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")))

ElementRef is an alias of core.ElementRef, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type ElementRef struct {
    // contains filtered or unexported fields
}

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.UseMount(func() func() {
        input.Focus()
        return nil
    })
    return Div(Ref(input), TabIndex(0), Class("rounded-md p-2 focus:ring-2"), Text("Search"))
}

func (ElementRef) Blur

func (r ElementRef) Blur()

Blur removes the focus from the element if it has it (HTML's blur()): nothing is focused afterwards. Inside an active containing focus scope it does nothing: the scope keeps the focus (React Aria's FocusScope contain focuses the element again).

func (ElementRef) Bounds

func (r ElementRef) Bounds() Rect

Bounds returns the border box of the element in window coordinates and logical pixels, as it was last laid out and painted: moved by the scroll offsets of the containers around it and by its own transform and those of its ancestors, as the axis-aligned bounding box of the transformed box (getBoundingClientRect). The window's top-left corner is (0, 0). An inline element (a Span in a paragraph) has the union of its fragments, and an element with display: contents that of its children. It is the zero Rect for an element that is not mounted, is display: none, or has not been laid out yet. Call it from event handlers and effects; UseBounds gives a Value that follows it.

func (ElementRef) Click

func (r ElementRef) Click()

Click clicks the element like HTML's element.click(): a click event (the primary button, a click count of 1, at the center of the element's bounds, made by no pointer: the hooks of package aria see a virtual press) is dispatched at it and bubbles, then, unless a handler prevented it, the activation behavior of the Button or Label at or above it runs (a Button in a Form submits it or resets it by its Type; a Label focuses its control and clicks it). There are no pointer events, no :active state and no focus change of its own. It does nothing for an unmounted element or a disabled one (or one inside a disabled element).

Call it on the UI thread, in a handler or an effect: the updates the click makes are applied and built by the event or the frame that is running (after the passive effects, by the host's next step, which the updates wake); called from elsewhere on the UI thread, it applies and builds them before it returns, as a real click does.

func (ElementRef) Focus

func (r ElementRef) Focus()

Focus focuses the element, if it is focusable (HTML's focus()): OnBlur runs on the element that loses the focus and OnFocus on this one, before it returns. Called from a handler or an effect, the updates those handlers make are applied and built by the event or the frame that is running (after the passive effects, by the host's next step), as for Click.

func (ElementRef) Focused

func (r ElementRef) Focused() bool

Focused reports whether the element is the focused element of its window (also while the window itself does not have keyboard focus).

func (ElementRef) ScrollBy

func (r ElementRef) ScrollBy(dx, dy float32)

ScrollBy scrolls the element by (dx, dy) from its current offsets (see ScrollTo).

func (ElementRef) ScrollIntoView

func (r ElementRef) ScrollIntoView()

ScrollIntoView scrolls every scroll container around the element so that it is visible, moving each as little as possible (CSSOM View's scrollIntoView with block and inline "nearest"); each container jumps or animates as its scroll-behavior says. It does nothing when the element is not mounted or not displayed. Called from a layout effect, it scrolls with the layout and extents of that frame, before paint.

func (ElementRef) ScrollOffset

func (r ElementRef) ScrollOffset() (x, y float32)

ScrollOffset returns the element's scroll offsets (the DOM's scrollLeft and scrollTop): zero when it is not a scroll container or not mounted. During a smooth scroll it is the current, animated position. In a right-to-left container x is 0 at the right and negative toward the left.

func (ElementRef) ScrollTo

func (r ElementRef) ScrollTo(x, y float32)

ScrollTo scrolls the element to offsets (x, y), clamped to its extents (CSSOM View's scrollTo): at once, or animated when it has scroll-smooth. It does nothing for an element that is not a scroll container (overflow auto, scroll or hidden) or not mounted. In a right-to-left container x is 0 at the right and negative toward the left, like the DOM's scrollLeft. Called from a layout effect, it applies in the same frame with the extents of that frame's layout, before paint.

func (ElementRef) ScrollToIndex

func (r ElementRef) ScrollToIndex(i int, align ScrollAlign)

ScrollToIndex scrolls a VirtualList so that item i is in view, placed as align says (TanStack Virtual's scrollToIndex; see the comment at the top of virtual.go): at once, or animated when the list has scroll-smooth, like ScrollTo. i is clamped to the items. It does nothing when the element is not a mounted VirtualList, has no items or does not scroll (overflow-visible).

func (ElementRef) Select

func (r ElementRef) Select()

Select selects the whole value of the text field the ref is attached to (HTML's select()) and fires OnSelect; it does nothing on other elements.

func (ElementRef) SelectionRange

func (r ElementRef) SelectionRange() (start, end int)

SelectionRange returns the selection of the text field the ref is attached to in UTF-8 byte offsets of its value, ordered (HTML's selectionStart and selectionEnd); 0, 0 for other elements.

func (ElementRef) SetSelectionRange

func (r ElementRef) SetSelectionRange(start, end int)

SetSelectionRange selects the bytes [start, end) of the value of the text field the ref is attached to (UTF-8 byte offsets, clamped to the value and moved to grapheme cluster boundaries; start > end selects nothing at end), as HTML's setSelectionRange, and fires OnSelect when it changed. It does nothing on other elements.

func (ElementRef) Value

func (r ElementRef) Value() string

Value returns the value of the text field the ref is attached to (with the text an IME is composing, as the DOM's input.value), "" for other elements: mainly for uncontrolled fields.

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) }),
    ...)

FocusEvent is an alias of core.FocusEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type FocusEvent struct {
    // contains filtered or unexported fields
}

func (FocusEvent) DefaultPrevented

func (e FocusEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (FocusEvent) PreventDefault

func (e FocusEvent) PreventDefault()

PreventDefault marks the event; focus events have no default action to cancel (the focus has already moved).

func (FocusEvent) StopPropagation

func (e FocusEvent) StopPropagation()

StopPropagation stops the event from bubbling to the ancestors.

type Gradient

type Gradient = style.Gradient

Gradient is a background gradient; build one with LinearGradient.

Gradient is an alias of style.Gradient, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Gradient struct {
    Kind GradientKind

    // Angle is the direction of a linear gradient in degrees (0 is up, 90
    // is to the right) when Corner is CornerNone, and the start angle
    // ("from") of a conic gradient.
    Angle  float32
    Corner Corner

    // Radial shape, size and (for SizeExplicit) radii; a circle uses
    // Radius[0] only.
    Shape  RadialShape
    Size   RadialSize
    Radius [2]LP

    // Center is the position of a radial or conic gradient inside the
    // box, from its top-left corner. The default is 50% 50%.
    CenterX, CenterY LP

    // Interp is the color space of the interpolation. The zero value is
    // Oklab, the default of Tailwind's named gradient utilities; gradients
    // written as CSS functions default to sRGB, as in CSS (ADR-158).
    Interp gfx.ColorSpace

    Stops []ColorStop
}

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.

func (*Gradient) Paint

func (g *Gradient) Paint(box gfx.Rect) gfx.Paint

Paint returns the paint of the gradient for a box with the given rectangle, in the coordinates of that rectangle (the color of a point of the gradient is the same as CSS paints in a box of this size).

type GradientStop

type GradientStop = style.ColorStop

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

GradientStop is an alias of style.ColorStop, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type ColorStop struct {
    Color gfx.Color
    Pos   StopPos
}

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 Icon

type Icon struct {
    SVG string
}

Icon is an SVG icon, shown with h.Icon. Its SVG field holds the text of an SVG document, so that icons can be package-level data:

var Check = tela.Icon{SVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
  stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/></svg>`}

The document is parsed the first time an icon with that text is drawn and the result is cached by the text for the life of the process (in a cache bounded by size), so drawing it again, at any size or color, never parses it again. Icon is comparable. ParseSVG and MustSVG check the text when the icon is created.

Icons are drawn as vectors at the device scale, with currentColor as the element's text color; see h.Icon.

func MustSVG

func MustSVG(data []byte) Icon

MustSVG is ParseSVG for documents known to be valid, such as embedded files: it panics on error.

//go:embed logo.svg
var logoSVG []byte
var Logo = tela.MustSVG(logoSVG)

func ParseSVG

func ParseSVG(data []byte) (Icon, error)

ParseSVG parses data as an SVG document and returns it as an Icon. The parsed document is cached, so the first draw does not parse it again. It returns the parse error of malformed XML, a root that is not svg, or a document beyond Tela's limits on size.

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()
    }
}))

InputEvent is an alias of core.InputEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type InputEvent struct {
    InputType   string
    Data        string
    IsComposing bool
    // contains filtered or unexported fields
}

func (InputEvent) DefaultPrevented

func (e InputEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (InputEvent) PreventDefault

func (e InputEvent) PreventDefault()

PreventDefault cancels the edit (not during a composition, nor an IME's deletion around the caret).

func (InputEvent) StopPropagation

func (e InputEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

type KeyCode

type KeyCode = core.CodeValue

KeyCode is a physical key, the type of KeyboardEvent.Code: 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() }

KeyCode is an alias of core.CodeValue, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type CodeValue string
const (
    // CodeUnidentified is a key without a W3C code value.
    CodeUnidentified KeyCode = "Unidentified"

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

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

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

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

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

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

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

    // Legacy, non-standard and special keys.
    CodeHyper    KeyCode = "Hyper"
    CodeSuper    KeyCode = "Super"
    CodeTurbo    KeyCode = "Turbo"
    CodeAbort    KeyCode = "Abort"
    CodeResume   KeyCode = "Resume"
    CodeSuspend  KeyCode = "Suspend"
    CodeAgain    KeyCode = "Again"
    CodeCopy     KeyCode = "Copy"
    CodeCut      KeyCode = "Cut"
    CodeFind     KeyCode = "Find"
    CodeOpen     KeyCode = "Open"
    CodePaste    KeyCode = "Paste"
    CodeProps    KeyCode = "Props"
    CodeSelect   KeyCode = "Select"
    CodeUndo     KeyCode = "Undo"
    CodeHiragana KeyCode = "Hiragana"
    CodeKatakana KeyCode = "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). KeyCode is a string type: e.Code == "KeyS" and e.Code == tela.CodeKeyS 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.

KeyLocation is an alias of core.KeyLocation, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

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

The key locations, with the DOM's values.

func (KeyLocation) String

func (l KeyLocation) String() string

String returns "standard", "left", "right" or "numpad".

type KeyName

type KeyName = core.KeyValue

KeyName is a logical key, the type of KeyboardEvent.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 == "+" { ... }

KeyName is an alias of core.KeyValue, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type KeyValue string
const (
    // KeyUnidentified is a key with neither a name nor a character.
    KeyUnidentified KeyName = "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        KeyName = "Alt"
    KeyAltGraph   KeyName = "AltGraph"
    KeyCapsLock   KeyName = "CapsLock"
    KeyControl    KeyName = "Control"
    KeyFn         KeyName = "Fn"
    KeyFnLock     KeyName = "FnLock"
    KeyMeta       KeyName = "Meta"
    KeyNumLock    KeyName = "NumLock"
    KeyScrollLock KeyName = "ScrollLock"
    KeyShift      KeyName = "Shift"
    KeySymbol     KeyName = "Symbol"
    KeySymbolLock KeyName = "SymbolLock"
    KeyHyper      KeyName = "Hyper"
    KeySuper      KeyName = "Super"

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

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

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

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

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

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

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

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

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

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

    // Browser keys.
    KeyBrowserBack      KeyName = "BrowserBack"
    KeyBrowserFavorites KeyName = "BrowserFavorites"
    KeyBrowserForward   KeyName = "BrowserForward"
    KeyBrowserHome      KeyName = "BrowserHome"
    KeyBrowserRefresh   KeyName = "BrowserRefresh"
    KeyBrowserSearch    KeyName = "BrowserSearch"
    KeyBrowserStop      KeyName = "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". KeyName is a string type: e.Key == "Enter" and e.Key == tela.KeyEnter mean the same.

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). CmdOrCtrl reports the platform's shortcut modifier: Command (Meta) on a Mac, Ctrl elsewhere (ADR-221); MouseEvent, PointerEvent, WheelEvent and DragEvent have it too. 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.CmdOrCtrl() && e.Key == "a": // Cmd+A on a Mac, Ctrl+A elsewhere
        selectAll()
    }
})

KeyboardEvent is an alias of core.KeyboardEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type KeyboardEvent struct {
    // Key is the logical key: "Enter", "Escape", "a", "A", " "... It
    // reflects Shift, AltGr and the lock keys, but not Ctrl: Ctrl+S has
    // Key "s" (Ctrl+Shift+S: "S").
    Key KeyValue
    // Code is the physical key ("KeyS", "Enter", "ShiftLeft"), the same
    // whatever the layout and modifiers.
    Code CodeValue
    // Location tells left and right modifiers and the numeric keypad
    // apart.
    Location KeyLocation
    // Repeat marks a keydown generated by key repeat while the key is
    // held.
    Repeat bool
    // Modifier keys held after the event (the DOM's rule: the keydown of
    // Shift has Shift set, its keyup does not); Meta is the Windows,
    // Command or Super key.
    Shift, Ctrl, Alt, Meta bool
    // contains filtered or unexported fields
}

func (KeyboardEvent) CmdOrCtrl

func (e KeyboardEvent) CmdOrCtrl() bool

CmdOrCtrl reports whether the primary modifier of shortcuts is held: Meta (Command) under the Mac flavor, Ctrl under the others, so e.CmdOrCtrl() && e.Key == "s" is Command+S on a Mac and Ctrl+S elsewhere. The flavor is the one of the app that dispatched the event: the OS's, or another platform's in a test (telatest.PlatformKeys; Config.Flavor in this package); an event built by hand follows the OS. It tests only that modifier, like React Aria's isCtrlKeyPressed and Playwright's ControlOrMeta, and is named after Electron's CmdOrCtrl accelerator modifier (ADR-221).

func (KeyboardEvent) DefaultPrevented

func (e KeyboardEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (KeyboardEvent) PreventDefault

func (e KeyboardEvent) PreventDefault()

PreventDefault cancels the default action of the key: the focus move of Tab, the click of Enter or Space on a button.

func (KeyboardEvent) StopPropagation

func (e KeyboardEvent) StopPropagation()

StopPropagation stops the event from bubbling to the ancestors of the element whose handler runs. The other handlers of that element still run.

type MouseButton

type MouseButton = core.MouseButton

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

MouseButton is an alias of core.MouseButton, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

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

The mouse buttons.

func (MouseButton) String

func (b MouseButton) String() string

String returns the button name: "left", "right"...

type MouseButtons

type MouseButtons = core.MouseButtons

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

MouseButtons is an alias of core.MouseButtons, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type MouseButtons uint8

func (MouseButtons) Has

func (s MouseButtons) Has(b MouseButton) bool

Has reports whether b is in the set.

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).

MouseEvent is an alias of core.MouseEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type MouseEvent struct {
    // X and Y are the pointer position in logical pixels, relative to the
    // top-left corner of the window's content area.
    X, Y float32
    // LocalX and LocalY are the position relative to the border box of
    // the element whose handler runs (the DOM's currentTarget), in its own
    // coordinates (its transforms and its ancestors' undone).
    LocalX, LocalY float32
    // Button is the button that changed state for a press, release or
    // click; MouseButtonNone for moves, enter and leave.
    Button MouseButton
    // Buttons are the buttons held after the event.
    Buttons MouseButtons
    // Modifier keys held during the event; Meta is the Windows, Command
    // or Super key.
    Shift, Ctrl, Alt, Meta bool
    // ClickCount is the number of consecutive clicks (the DOM's detail):
    // 1 for a single click, 2 for the second click of a double click.
    // Presses and releases carry the count of the click they belong to;
    // moves, enter and leave carry 0.
    ClickCount int
    // contains filtered or unexported fields
}

func (MouseEvent) CmdOrCtrl

func (e MouseEvent) CmdOrCtrl() bool

CmdOrCtrl reports whether the primary modifier of shortcuts is held: Meta (Command) under the Mac flavor, Ctrl under the others. The flavor is the one of the app that dispatched the event: the OS's, or another platform's in a test (telatest.PlatformKeys; Config.Flavor in this package); an event built by hand follows the OS. It tests only that modifier, like React Aria's isCtrlKeyPressed and Playwright's ControlOrMeta, and is named after Electron's CmdOrCtrl accelerator modifier (ADR-221).

func (MouseEvent) DefaultPrevented

func (e MouseEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (MouseEvent) PreventDefault

func (e MouseEvent) PreventDefault()

PreventDefault cancels Tela's default action for the event and marks it for the handlers that run after (DefaultPrevented). Cancelling OnPointerDown suppresses the mouse events of the press (OnMouseDown, OnMouseMove, OnMouseUp), but not OnClick, as in the DOM.

func (MouseEvent) StopPropagation

func (e MouseEvent) StopPropagation()

StopPropagation stops the event from bubbling to the ancestors of the element whose handler runs. The other handlers of that element still run.

type Node

type Node = core.Node

Node is what renders: a value whose Build method returns what it renders (ADR-205). The elements of package h (h.Div, h.Text, h.Fragment...) and the components made with C, CP and Memo are Nodes, and so is every struct component, a struct with a Build method:

type Card struct {
    Title    string
    OnOpen   tela.Callback[func()]
    Children tela.Node // children are an ordinary field
}

func (c Card) Build() tela.Node {
    open, setOpen := tela.UseState(false)
    return Div(Class("rounded-xl border p-4"),
        H2(OnClick(func(tela.MouseEvent) { setOpen.Set(!open) }), Text(c.Title)),
        If(open, c.Children))
}

func Inbox() tela.Node { return Card{Title: "Inbox", Children: Text("a")} }

A Node is an immutable, cheap description of UI: function components return one, Build returns one, and every build creates new ones; the persistent state lives elsewhere, keyed by position and key (ARCHITECTURE section 6). A struct component can be returned, kept in a []tela.Node, given to h.Map or to a field of type Node as it is.

nil 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; h.If and h.IfElse return nil for a branch that is not taken, and n == nil tells it.

Every struct component is a component of its own, with its own hook state: hooks called in Build (and in the function literals inside it) belong to it. Build must use a value receiver. Its identity among its siblings is its type and its key (WithKey), so at one position a value of the same type keeps the state of the previous build and a value of another type mounts anew. When its parent builds again, its build is skipped if the new value equals the previous one and no state, Value or context it read changed, as with CP. The comparison is ==, field by field, and never panics: a value that cannot be compared (a struct holding a slice, a map or a func, or a field of interface type holding one) counts as changed, so it always builds. An element (h.Div(...)) in a field compares by identity: a fresh one is a change, a node kept in a UseMemo is not. Pass Callback values instead of funcs to keep a component skippable.

Calling Build directly (Card{}.Build() inside another component) runs it inside the caller, whose hook slots it would take: dev mode panics and says to return or pass Card{...} instead. A Build that returns its own receiver (return c) would build itself forever: dev mode panics.

In dev mode (see App.Dev) a pointer (Div(&Card{})) panics, since a component is a value that Tela compares, and so does a Prop held by a field of type Node (Card{Children: Class("p-4")}): a Prop has a Build method, so Go cannot tell it from a node at compile time, and given to an element it would apply to that element. A field that is meant to take props too is declared as Child. For the same reason a function that returns h.Class(...) where a Node is expected compiles, and panics when the node is used.

Two Nodes compare with == only when their dynamic types are comparable: n1 == n2 panics for two values of a struct component that holds a slice, a map or a func (n == nil never panics).

Node is an alias of core.Node, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Node interface {
    Build() Node
}

func C

func C(fn func() Node, key ...Child) 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, so its hooks would take the caller's slots: dev mode panics when a hook is called from a function that is neither the component being built (or a function literal inside it) nor a custom hook named UseXxx or useXxx, and says to wrap it.

Call C where the component is defined, in a public function over a private one, so that its users write Header() and not tela.C(header):

func Header() tela.Node { return tela.C(header) }

func header() tela.Node { ... }

Div(Header(), Body())

The optional last argument is the component's key, h.Key(id), for a component in a list: tela.C(row, Key(id)) is tela.WithKey(tela.C(row), id). Anything else but Key panics.

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, key ...Child) 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 { return tela.CP(card, p) }

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

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

Call CP where the component is defined, as above, so that its users never write it. Give CP a named function and pass everything it uses in its props: variables captured by a closure are not compared, so dev mode 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.

The optional last argument is the component's key, h.Key(id), for the items of a list: tela.CP(row, RowProps{...}, Key(row.ID)) is tela.WithKey(tela.CP(row, RowProps{...}), row.ID). Anything else but Key panics; the key is not a prop of the component and is not compared with the props.

Example (key)

t := &exampleT{}
defer t.close()
var set tela.Setter[[]exampleRowProps]
list := func() tela.Node {
    rows, s := tela.UseState([]exampleRowProps{{1, "Ada"}, {2, "Grace"}})
    set = s
    nodes := make([]tela.Node, len(rows))
    for i, r := range rows {
        // The key goes last: it is not a prop of the row.
        nodes[i] = tela.CP(exampleRow, r, Key(r.ID))
    }
    return Div(Nodes(nodes))
}
app := telatest.Mount(t, tela.C(list))
app.Click(telatest.ByText("Grace 0")) // Grace has been clicked once
set.Set([]exampleRowProps{{2, "Grace"}, {1, "Ada"}})
fmt.Println(app.AriaSnapshot())
// Output:
// - button "Grace 1"
// - button "Ada 0"

func Memo

func Memo[P any](fn func(P) Node, props P, equal func(a, b P) bool, key ...Child) 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.

func Chart(p ChartProps) tela.Node {
    return tela.Memo(chart, p, func(a, b ChartProps) bool {
        return slices.Equal(a.Points, b.Points)
    })
}

Like CP, it takes an optional last argument, the key h.Key(id).

func WithKey

func WithKey[K comparable](c Node, k K) Node

WithKey returns the node of c with the key k, whatever its kind: a node that cannot take the h.Key prop (a component or a fragment), or a struct component, WithKey(Card{...}, id). k is a comparable value, such as an ID: a slice or a map does not compile, and a nil interface, or one holding a value that cannot be compared, panics. A nil c gives nil; a Prop panics.

Map(cards, func(c CardData) tela.Node {
    return tela.WithKey(Card{Title: c.Title}, c.ID)
})

type Point

type Point = core.Point

Point is a point in logical pixels: X to the right, Y down. It is one of the Animatable types, for a position that moves as a whole, such as a dragged card's offset.

Point is an alias of core.Point, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Point struct{ X, Y float32 }

Example

t := &exampleT{}
defer t.close()
var pos *tela.Value[tela.Point]
card := func() tela.Node {
    pos = tela.UseValue(tela.Point{})
    return Div(Class("size-24 rounded-xl bg-white shadow"),
        Style(tela.TranslateFn(func() (float32, float32) { p := pos.Get(); return p.X, p.Y })),
        // Released: back to the origin on a bouncy spring.
        OnDragEnd(func(tela.DragEvent) { tela.Animate(pos, tela.Point{}, tela.Spring(tela.Bounce(0.4))) }),
        OnDrag(func(e tela.DragEvent) { pos.Set(tela.Point{X: e.OffsetX, Y: e.OffsetY}) }))
}
app := telatest.Mount(t, tela.C(card), telatest.NoSettle())
// Called between frames (like from a goroutine), Animate starts at the
// next frame: 50 ms later the tween has run for 33 ms of its 100.
tela.Animate(pos, tela.Point{X: 40, Y: 30}, tela.Duration(100*time.Millisecond), tela.Ease("linear"))
app.Advance(50 * time.Millisecond)
fmt.Printf("%.0f %.0f\n", pos.Get().X, pos.Get().Y)
// Output: 13 10

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.

PointerEvent is an alias of core.PointerEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type PointerEvent struct {
    // X and Y are the position in the window, LocalX and LocalY relative
    // to the border box of the element whose handler runs (see
    // MouseEvent).
    X, Y, LocalX, LocalY float32
    // Button is the button that changed state (MouseButtonNone for moves,
    // enter and leave); Buttons the buttons held after the event.
    Button  MouseButton
    Buttons MouseButtons
    // Modifier keys held during the event.
    Shift, Ctrl, Alt, Meta bool
    // Kind is the device: mouse, touch or pen.
    Kind PointerKind
    // PointerID identifies the pointer among those present: the mouse is
    // always 1; each touch contact has its own.
    PointerID int
    // Pressure is the normalized pressure, 0 to 1: 0.5 while a button is
    // held and 0 otherwise when the device does not report it (the DOM's
    // rule; no backend reports pressure yet).
    Pressure float32
    // IsPrimary reports the primary pointer of its kind: the mouse, the
    // first touch contact, the pen. Mouse events are fired for it only.
    IsPrimary bool
    // contains filtered or unexported fields
}

func (PointerEvent) CmdOrCtrl

func (e PointerEvent) CmdOrCtrl() bool

CmdOrCtrl reports whether the primary modifier of shortcuts is held (see MouseEvent.CmdOrCtrl).

func (PointerEvent) DefaultPrevented

func (e PointerEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (PointerEvent) PreventDefault

func (e PointerEvent) PreventDefault()

PreventDefault cancels the default action (see MouseEvent).

func (PointerEvent) StopPropagation

func (e PointerEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

type PointerKind

type PointerKind = core.PointerKind

PointerKind is the device behind a pointer.

PointerKind is an alias of core.PointerKind, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type PointerKind uint8
const (
    PointerMouse PointerKind = iota // a mouse or touchpad cursor
    PointerTouch                    // a finger on a touchscreen
    PointerPen                      // a stylus
)

The pointer kinds.

func (PointerKind) String

func (k PointerKind) String() string

String returns "mouse", "touch" or "pen".

type Preview

type Preview struct {
    // Root is the screen. It must not be nil.
    Root Component
    // Wrap, when not nil, receives the node of Root and returns what is
    // mounted instead, normally Root inside the Provider of every context
    // it reads. It runs each time the preview builds and may not use
    // hooks: put state in a component.
    Wrap func(Node) Node
}

Preview is one screen of a package for tela snapshot, tela inspect and Previews: the component to show, and optionally a wrapper that supplies what the screen reads from context.

Root is a Component: a node such as Settings(), a function that returns tela.C(settings), or a struct component value. The local state of a preview is seeded through the fields of a struct component, with no global:

type Main struct{ InitialTab string }

func (m Main) Build() tela.Node {
    tab, setTab := tela.UseState(m.InitialTab)
    ...
}

var Previews = tela.Previews{
    "open": {Root: Main{InitialTab: "open"}},
}

State that comes from a context is seeded with Wrap, which receives the root as a node and returns it inside the providers the screen needs:

var Previews = tela.Previews{
    "mocked": {
        Root: Main{},
        Wrap: func(n tela.Node) tela.Node { return AppState.Provider(mockState, n) },
    },
}

A Preview is itself a Component that builds Wrap(Root): the CLI mounts exactly that, and a test mounts a preview the way the CLI does with telatest.Mount(t, Previews["mocked"]).

Example

// A Preview is a Component: mount it as `tela snapshot` does.
for _, name := range inboxPreviews.Names() {
    yaml, _ := telatest.AriaSnapshot(inboxPreviews[name])
    fmt.Printf("%s: %s\n", name, strings.TrimPrefix(yaml, "- text: "))
}
// Output:
// closed: guest sees the closed tab
// mocked: Ana sees the open tab
// open: guest sees the open tab

func (Preview) Build

func (p Preview) Build() Node

Build returns the node the preview mounts: Root, or Wrap(Root) when Wrap is set. It panics when Root is nil.

type Previews

type Previews map[string]Preview

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":    {Root: LoginScreen()},
    "Settings": {Root: Settings{InitialTab: "account"}},
}

Each Root is 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, which is built as a component of its own (it may use hooks). See Preview to start a screen in a given state.

Example

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

previews := tela.Previews{
    "Settings": {Root: tela.C(settings)},
    "Login":    {Root: tela.C(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. A Prop is a Child (and, by its method set, a Node, although it renders nothing: dev mode reports one held by a field of type Node, and one used where a node is expected panics).

Prop is an alias of core.Prop, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Prop struct {
    // contains filtered or unexported fields
}

func MergeProps

func MergeProps(props ...Child) Prop

MergeProps returns one Prop that applies props in order, for a place that takes a single one: kit's Props field of type Child, or a hook's result next to your own props. Event handlers accumulate (every handler of an event runs, in order); the other props are set by the last one that sets them, as when they are given one after the other to an element. Nil and the zero Prop are skipped.

It takes only props, and style props such as tela.Width. A Node (an element or a struct component) is not a prop and cannot be merged into one: dev mode panics, naming the argument, and otherwise it is ignored.

kit.Button{Children: Text("Save"), Props: tela.MergeProps(
    TestID("save"), OnMouseEnter(prefetch), AriaLabel("Save the draft"))}

Example

Several props where one is taken: a kit component's Props field.

t := &exampleT{}
defer t.close()
page := func() tela.Node {
    return Div(
        Button(Class("rounded border px-3"), Text("Save"),
            tela.MergeProps(
                TestID("save"),
                AriaLabel("Save the draft"),
                OnClick(func() { fmt.Println("clicked") }),
                OnClick(func() { fmt.Println("and again") }),
            )))
}
app := telatest.Mount(t, tela.C(page))
app.Click(telatest.ByTestID("save"))
app.ExpectRole("button", "Save the draft")
// Output:
// clicked
// and again

func (Prop) Build

func (p Prop) Build() Node

Build returns a node that holds p: given to an element (or to a Fragment, a Provider or a Presence, which take Key only) it applies like p itself; where a node is consumed (nodeOf) it panics, since a prop renders nothing. It is what makes Prop a Child.

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.

Rect is an alias of core.Rect, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Rect struct{ X, Y, Width, Height float32 }

func (Rect) Bottom

func (r Rect) Bottom() float32

Bottom returns the y of the bottom edge.

func (Rect) Contains

func (r Rect) Contains(x, y float32) bool

Contains reports whether the point (x, y) is inside r (the right and bottom edges are outside).

func (Rect) Right

func (r Rect) Right() float32

Right returns the x of the right edge.

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.

Ref is an alias of core.Ref, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Ref[T any] struct {
    Current T
}

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 SchemeColor

type SchemeColor = core.SchemeColor

SchemeColor is the value of a theme color in the light and the dark color scheme.

tela.SchemeColor{Light: tela.Hex("#fafafa"), Dark: tela.Hex("#0a0a0a")}

SchemeColor is an alias of core.SchemeColor, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type SchemeColor = style.SchemeColor

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).

ScrollAlign is an alias of core.ScrollAlign, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type ScrollAlign uint8
const (
    // AlignAuto scrolls as little as possible: nothing when the item is
    // fully visible, else to align its end (when it reaches past the
    // bottom) or its start.
    AlignAuto ScrollAlign = iota
    // AlignStart puts the item's top at the top of the scrollport.
    AlignStart
    // AlignCenter centers the item in the scrollport.
    AlignCenter
    // AlignEnd puts the item's bottom at the bottom of the scrollport.
    AlignEnd
)

func (ScrollAlign) String

func (a ScrollAlign) String() string

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)

ScrollEvent is an alias of core.ScrollEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type ScrollEvent struct {
    // ScrollLeft and ScrollTop are the scroll offsets: how far the
    // content is scrolled from its start. In a right-to-left container
    // (Dir("rtl")) the start is the right: ScrollLeft is 0 there and
    // negative toward the left, as in the DOM.
    ScrollLeft, ScrollTop float32
    // ScrollWidth and ScrollHeight are the size of the scrollable content
    // (at least the client size); the largest offsets are ScrollWidth -
    // ClientWidth and ScrollHeight - ClientHeight.
    ScrollWidth, ScrollHeight float32
    // ClientWidth and ClientHeight are the size of the visible area: the
    // padding box without the scrollbars' gutters.
    ClientWidth, ClientHeight float32
    // contains filtered or unexported fields
}

func (ScrollEvent) DefaultPrevented

func (e ScrollEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (ScrollEvent) PreventDefault

func (e ScrollEvent) PreventDefault()

PreventDefault marks the event; the scroll already happened.

func (ScrollEvent) StopPropagation

func (e ScrollEvent) StopPropagation()

StopPropagation does nothing: scroll events do not bubble. It exists for symmetry with the other events.

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.

SelectEvent is an alias of core.SelectEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type SelectEvent struct {
    Start, End int
    // contains filtered or unexported fields
}

func (SelectEvent) DefaultPrevented

func (e SelectEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (SelectEvent) PreventDefault

func (e SelectEvent) PreventDefault()

PreventDefault marks the event; the selection already changed.

func (SelectEvent) StopPropagation

func (e SelectEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

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, and IsZero reports it (a Setter field left unset, or one read from a context default without a Provider).

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.

Setter is an alias of core.Setter, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Setter[T any] struct {
    // contains filtered or unexported fields
}

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.

func (Setter) IsZero

func (st Setter[T]) IsZero() bool

IsZero reports whether st is the zero Setter, which is not tied to any state: its methods panic. A Setter left unset in a struct, or read from a context default without a Provider, is zero.

func (Setter) Set

func (st Setter[T]) Set(v T)

Set queues setting the state to v. For comparable types, a value == to the state when the update applies does not rebuild.

func (Setter) Update

func (st Setter[T]) Update(f func(T) T)

Update queues f, which receives the state left by the previous queued update and returns the new one: two queued Update(inc) add 2. f runs on the UI thread when the update applies, and must be pure (no side effects: it may run later than the call).

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.

Shadow is an alias of style.Shadow, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Shadow struct {
    OffsetX float32
    OffsetY float32
    Blur    float32
    Spread  float32
    Color   gfx.Color
    Inset   bool
}

type Size

type Size = core.Size

Size is a width and a height in logical pixels. It is one of the Animatable types.

Size is an alias of core.Size, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Size struct{ Width, Height float32 }

Example

t := &exampleT{}
defer t.close()
var size *tela.Value[tela.Size]
app := telatest.Mount(t, tela.C(func() tela.Node {
    size = tela.UseSpring(tela.Size{Width: 200, Height: 120}, tela.Initial(tela.Size{Width: 100, Height: 40}))
    return Div(Class("rounded-lg bg-sky-500"), Style(
        tela.WidthFn(func() float32 { return size.Get().Width }),
        tela.HeightFn(func() float32 { return size.Get().Height })))
}))
// Mount settles: the spring from the initial size to the target ended.
wBox := app.Bounds(telatest.ByClass("bg-sky-500"))
w, h := wBox.Width, wBox.Height
fmt.Println(w, h)
// Output: 200 120

type StopHandler

type StopHandler func()

StopHandler is the handler tela.StopPropagation returns. It is a named func() type, so every h.On… handler prop accepts it: the prop calls e.StopPropagation() on the event and then the function.

func StopPropagation

func StopPropagation(f func()) StopHandler

StopPropagation turns f into a handler that stops the event from bubbling to the ancestors' handlers and then calls f: the one-line form of a handler that begins with e.StopPropagation(). It works in every handler prop (OnClick, OnKeyDown, OnPointerDown, OnChange...), whatever the event type, and f does not take the event:

Button(OnClick(tela.StopPropagation(ctrl.Remove)), Text("Delete")) // the row's OnClick does not run
Div(OnKeyDown(tela.StopPropagation(close)))

A handler that needs the event, to stop propagation only for some events, calls e.StopPropagation() itself. The press hook of the aria package (aria.UsePress) stops propagation by default, as React Aria does. StopPropagation(nil) panics.

type StyleProp

type StyleProp = core.StyleProp

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.

A StyleProp is a Child: besides h.Style(tela.Width(10)), an element takes it directly, Div(tela.Width(10), tela.Bg(c)), which is the same (and h.If(cond, tela.Opacity(0.5)) works). Box and WH return a StyleProp of several declarations.

StyleProp is an alias of core.StyleProp, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type StyleProp struct {
    // contains filtered or unexported fields
}

Example

t := &exampleT{}
defer t.close()
// A StyleProp is a child: no Style(...) around it, and If takes it.
selected := true
app := telatest.Mount(t, Div(TestID("swatch"), Class("size-8"),
    tela.Bg(tela.Hex("#336699")), If(selected, tela.Opacity(0.5))),
    telatest.Size(100, 100))
fmt.Println(app.Bounds(telatest.ByTestID("swatch")).Width, app.PixelAt(16, 16))
// Output: 32 {153 178 204 255}

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 BgFn

func BgFn(fn func() Color) StyleProp

BgFn defers Bg to the paint phase: the element's background color is fn's, and a change of the Values fn reads repaints the element without building, computing styles or laying out. Like the other Fn props it wins over the background classes, and over their transitions. With a UseSpring or Animate of a Color it animates a background without rebuilding:

bg := tela.UseSpring(target) // target is a tela.Color
Div(Class("rounded-lg p-4"), Style(tela.BgFn(bg.Get)))

Example

t := &exampleT{}
defer t.close()
var bg *tela.Value[tela.Color]
app := telatest.Mount(t, tela.C(func() tela.Node {
    bg = tela.UseValue(tela.Hex("#ffffff"))
    return Div(Class("size-10"), Style(tela.BgFn(bg.Get)))
}), telatest.Size(40, 40))
before := app.Stats()
bg.Set(tela.Hex("#4f46e5"))
after := app.Stats() // settles: the frame that shows the change
// The change repainted the box: no build, style or layout.
fmt.Println(app.Screenshot().RGBAAt(5, 5), after.Builds-before.Builds, after.Styles-before.Styles, after.Layouts-before.Layouts)
// Output: {79 70 229 255} 0 0 0

func BgImage

func BgImage(img image.Image) StyleProp

BgImage sets a decoded image as the background image, over the background color. The background classes apply to it as to a bg-[url(...)] image: bg-cover, bg-contain, bg-center, bg-no-repeat, bg-origin-*, bg-clip-* and the others. Each image.Image value is converted once and cached (the cache keeps the most recently used images, up to 256 MiB of pixels), so pass the same value on every build and do not modify its pixels afterwards: give a new image instead. For images stored as files, register them with Assets and use a class.

Example

// A decoded image, such as one from image.Decode or generated in
// code. Pass the same value on every build: it is converted once.
photo := image.NewRGBA(image.Rect(0, 0, 4, 4))
for i := range 16 {
    photo.Set(i%4, i/4, color.RGBA{B: 255, A: 255})
}

// With package h: Div(Class("size-8 bg-cover bg-center"), Style(tela.BgImage(photo))).
card := core.Primitive(style.KindDiv, core.Class("size-8 bg-cover bg-center"), tela.BgImage(photo))
_, img := core.Render(card, core.RenderOptions{Width: 32, Height: 32})
fmt.Println(img.RGBAAt(16, 16))
// Output: {0 0 255 255}

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 Box

func Box(x, y, w, h float32) StyleProp

Box places and sizes a positioned element in pixels: its Left and Top insets and its Width and Height, in one StyleProp (the typed form of "left-[x] top-[y] w-[w] h-h"; the element needs a position class such as absolute or relative, and a rectangle from ElementRef.Bounds or UseBounds fits it).

Div(Class("absolute rounded-md ring-2 ring-indigo-500"), tela.Box(r.X, r.Y, r.Width, r.Height))

Example

t := &exampleT{}
defer t.close()
app := telatest.Mount(t, Div(Class("relative h-40 w-60"),
    Div(TestID("marker"), Class("absolute rounded ring-2 ring-indigo-500"), tela.Box(20, 10, 80, 30))),
    telatest.Size(240, 160))
fmt.Println(app.Bounds(telatest.ByTestID("marker")))
// Output: {20 10 80 30}

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

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 TextColorFn

func TextColorFn(fn func() Color) StyleProp

TextColorFn defers TextColor to the paint phase: a change of the Values fn reads repaints the text without building, computing styles or laying out. The element's text takes fn's color, and so does the text of its descendants that inherits the element's color: those whose classes (and the classes of the elements between) set no text color of their own, as CSS inheritance would give it. Underlines and other decorations without a color of their own follow the text. Colors that the classes derive from the text color when the style is computed (currentColor borders, outlines, ring colors) and the text of h.Input and h.Textarea keep the color their classes give. It wins over the text color classes and their transitions.

color := tela.UseTween(target, tela.Duration(200*time.Millisecond)) // a tela.Color
P(Style(tela.TextColorFn(color.Get)), Text("Status: "), Strong(Text("online")))

Example

t := &exampleT{}
defer t.close()
var c *tela.Value[tela.Color]
app := telatest.Mount(t, tela.C(func() tela.Node {
    c = tela.UseValue(tela.Hex("#0f172a"))
    // The Strong inherits the paragraph's color, so it follows the Fn
    // too; the Span has a color of its own.
    return P(Style(tela.TextColorFn(c.Get)),
        Text("Status: "), Strong(Text("online")), Span(Class("text-slate-400"), Text(" (auto)")))
}))
before := app.Stats()
c.Set(tela.Hex("#16a34a"))
after := app.Stats()
// Builds, styles, layouts and paints of the change.
fmt.Println(after.Builds-before.Builds, after.Styles-before.Styles, after.Layouts-before.Layouts, after.Paints-before.Paints)
// Output: 0 0 0 1

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 WH

func WH(w, h float32) StyleProp

WH sets the width and the height in pixels: WH(w, h) is Width(w) and Height(h) in one StyleProp. It is named for its two arguments because Size is the type of the animatable sizes.

Div(tela.WH(240, 160), Class("rounded-xl bg-white"))

Example

t := &exampleT{}
defer t.close()
app := telatest.Mount(t, Div(TestID("card"), Class("rounded-xl bg-white"), tela.WH(240, 160)),
    telatest.Size(300, 200))
fmt.Println(app.Bounds(telatest.ByTestID("card")))
// Output: {0 0 240 160}

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).

func (StyleProp) Build

func (p StyleProp) Build() Node

Build returns the node of Style(p): given to an element it applies the typed style props after the classes, like h.Style(p); mounted on its own it panics, since a prop renders nothing.

func (StyleProp) Ops

func (p StyleProp) Ops() []style.Op

Ops returns the ops of p, in order. The slice must not be modified.

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)
}), ...)

SubmitEvent is an alias of core.SubmitEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type SubmitEvent struct {
    // contains filtered or unexported fields
}

func (SubmitEvent) DefaultPrevented

func (e SubmitEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (SubmitEvent) PreventDefault

func (e SubmitEvent) PreventDefault()

PreventDefault marks the event.

func (SubmitEvent) StopPropagation

func (e SubmitEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

type Theme

type Theme struct {
    // Colors maps token names to their values in the light and the dark
    // scheme.
    Colors map[string]SchemeColor
    // Radius is the base radius in logical pixels, shadcn/ui's --radius:
    // rounded-lg is Radius, rounded-sm 0.6 x, rounded-md 0.8 x,
    // rounded-xl 1.4 x, rounded-2xl 1.8 x, rounded-3xl 2.2 x and
    // rounded-4xl 2.6 x (shadcn/ui v4's theme). 0 keeps Tailwind's radius
    // scale, which DefaultTheme does so that existing pages do not change;
    // set 10 for shadcn/ui's look.
    Radius float32
    // FontSans and FontMono are font family names for font-sans (also
    // the default text font) and font-mono (code, kbd, pre, samp); the
    // embedded Inter and JetBrains Mono stay as fallbacks. "" keeps the
    // defaults.
    FontSans, FontMono string
}

Theme holds an app's design tokens: semantic colors with a light and a dark value, the base radius and the font families. Classes name the colors like palette colors, with every color utility, opacity modifier, variant and transition: bg-primary, text-muted-foreground, border-border, ring-ring/50, hover:bg-primary/90, from-chart-1. A token takes the value of the color scheme of the element that uses it (App.ColorScheme, SetColorScheme, or the Scheme prop of an ancestor, h.Scheme), so one class serves both schemes, without dark: pairs.

The token names are shadcn/ui's (v4): background, foreground, card, card-foreground, popover, popover-foreground, primary, primary-foreground, secondary, secondary-foreground, muted, muted-foreground, accent, accent-foreground, destructive, border, input, ring and chart-1 to chart-5. DefaultTheme holds shadcn/ui's neutral values; start from it and change what you need:

theme := tela.DefaultTheme()
theme.Colors["primary"] = tela.SchemeColor{Light: tela.OKLCH(0.55, 0.2, 264), Dark: tela.OKLCH(0.7, 0.16, 264)}
theme.Colors["brand"] = tela.SchemeColor{Light: tela.Hex("#7c3aed"), Dark: tela.Hex("#a78bfa")} // bg-brand
theme.Radius = 10 // shadcn/ui's 0.625rem
tela.Main(tela.App{Title: "Notes", Root: Notes(), Theme: &theme, RootClass: "bg-background text-foreground"})

A token missing from Colors keeps its value in DefaultTheme, so the shadcn/ui tokens always resolve. A token name is lowercase letters, digits and dashes, and cannot be a palette color ("red-500", "white") or transparent, current or inherit; Run panics otherwise. A token cannot be written bare inside an arbitrary value (bg-[linear-gradient(...)]): write var(--color-primary) there (ADR-202), or use the utilities (from-primary, shadow-primary).

func DefaultTheme

func DefaultTheme() Theme

DefaultTheme returns the default theme: shadcn/ui's neutral colors (v4, converted from OKLCH to extended sRGB like the Tailwind palette), Tailwind's radius scale and the embedded fonts. It is the theme of an App whose Theme is nil. Every call returns a new map.

type ThemeValues

type ThemeValues struct {
    // contains filtered or unexported fields
}

ThemeValues are the theme's values at a component's position, from UseTheme: the colors resolved for the color scheme there. Use them where classes cannot go: canvases, images tinted in Go, computed styles.

func UseTheme

func UseTheme() ThemeValues

UseTheme returns the theme's values for the color scheme at the component's position: the Scheme prop of the nearest element above it that has one (h.Scheme), else the app's scheme. The component builds again when that scheme or the theme (SetTheme) changes. It is a hook: call it at the top level of a component.

func Spark() tela.Node {
    th := tela.UseTheme()
    line := th.Color("chart-1")
    return Div(Style(tela.Bg(th.Color("muted"))), ...)
}

func (ThemeValues) Color

func (t ThemeValues) Color(name string) Color

Color returns the value of a color token ("primary") or of a palette color ("red-500") in the scheme of the values. An unknown name is transparent; in dev mode it panics with the closest token name.

func (ThemeValues) FontMono

func (t ThemeValues) FontMono() string

FontMono returns the family of font-mono: the theme's FontMono, or the embedded "JetBrains Mono".

func (ThemeValues) FontSans

func (t ThemeValues) FontSans() string

FontSans returns the family of font-sans: the theme's FontSans, or the embedded "Inter".

func (ThemeValues) Lookup

func (t ThemeValues) Lookup(name string) (Color, bool)

Lookup returns the value of a color token or palette color, and whether the theme has it.

func (ThemeValues) Radius

func (t ThemeValues) Radius() float32

Radius returns the radius of rounded-lg in logical pixels: the theme's Radius, or Tailwind's 8 when the theme keeps Tailwind's scale.

func (ThemeValues) Scheme

func (t ThemeValues) Scheme() ColorScheme

Scheme returns the color scheme of the values, Light or Dark.

func (ThemeValues) Tokens

func (t ThemeValues) Tokens() []string

Tokens returns the names of the theme's color tokens, sorted.

type TitleBarColors

type TitleBarColors = core.TitleBarColors

TitleBarColors are the colors of the title bar and border the OS draws for a window (App.TitleBar, Window.TitleBar). A zero Color, or one with zero alpha, keeps the system's color; the alpha of any other is ignored (the color is used opaque).

tela.App{
    TitleBar: tela.TitleBarColors{
        Background: tela.Hex("#1e1b4b"),
        Text:       tela.Hex("#e0e7ff"),
    },
    ...
}

Windows 11 (build 22000 and later) applies them to the native title bar of a decorated window (Background, Text: the title and the glyphs of the caption buttons) and to its border (Border, also the thin border Windows 11 draws around a Frameless window). Windows 10, Linux (Wayland and X11) and headless ignore them, as does a decorated window on Wayland whose compositor draws no title bar for it. The dark or light title bar already follows the app's color scheme (App.ColorScheme); these colors are on top of it. Changing the field of an open Window updates the window.

TitleBarColors is an alias of core.TitleBarColors, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type TitleBarColors struct {
    Background, Text, Border gfx.Color
}

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.

Value is an alias of core.Value, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type Value[T any] struct {
    // contains filtered or unexported fields
}

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 UsePresenceTween

func UsePresenceTween(opts ...AnimationOption) *Value[float32]

UsePresenceTween is the entrance and the exit of a component inside a child of an h.Presence as one Value: a number that goes from 0 to 1 when the component mounts and from 1 to 0 while its child exits, and that calls UsePresence's done when the exit arrives, so the child unmounts then. Read it in deferred style closures and the component does not build while it moves.

The options are those of UseTween: Duration (300 ms by default), Ease, and OnDone, which runs after each of the two motions, before done. With spring options (Stiffness, Damping, Bounce, Velocity, Spring) it is a UseSpring instead. The entrance starts from 0, as with Initial; the same *Value is returned on every build.

A toast that slides up and fades in, and out again before it unmounts:

func Toast() tela.Node { return tela.C(toast) }

func toast() tela.Node {
    shown := tela.UsePresenceTween(tela.Duration(250*time.Millisecond), tela.Ease(tela.EaseOut))
    return Div(Class("fixed bottom-8 rounded-xl bg-slate-900 px-5 py-3 text-white"),
        Style(tela.TranslateFn(func() (float32, float32) { return 0, 24 * (1 - shown.Get()) }),
            tela.OpacityFn(shown.Get)),
        Text("Saved"))
}

Presence(If(saved, Toast()))

Example

t := &exampleT{}
defer t.close()
toast := func() tela.Node {
    shown := tela.UsePresenceTween(tela.Duration(200*time.Millisecond), tela.Ease(tela.EaseOut))
    return Div(Role("status"), Class("rounded-lg bg-slate-900 px-4 py-2 text-white"),
        Style(tela.TranslateFn(func() (float32, float32) { return 0, 24 * (1 - shown.Get()) }),
            tela.OpacityFn(shown.Get)),
        Text("Saved"))
}
page := func() tela.Node {
    saved, setSaved := tela.UseState(true)
    return Div(
        Button(OnClick(tela.Toggle(setSaved)), Text("Dismiss")),
        Presence(If(saved, tela.C(toast))))
}
app := telatest.Mount(t, tela.C(page))
fmt.Println(app.Count(telatest.ByRole("status", "")))
start := app.Now()
app.Click(telatest.ByRole("button", "Dismiss")) // waits for the exit tween
fmt.Println(app.Count(telatest.ByRole("status", "")), app.Now()-start >= 200*time.Millisecond)
// Output:
// 1
// 0 true

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 UseSize

func UseSize(ref ElementRef) *Value[Size]

UseSize returns a Value that follows the size of the element ref is attached to with h.Ref: its border box as laid out, like a ResizeObserver (borderBoxSize). Scrolling and transforms never change it, so it does not move while a list scrolls or a card animates its scale; it is the zero Size while the element is not mounted or not displayed, and for inline elements of a paragraph. Use it to size one element after another, or to make a popover as wide as its trigger.

The Value is updated in every frame that lays out, before painting: a deferred style closure of the paint phase that reads it (TranslateFn, OpacityFn, BgFn) follows in that frame; a component that reads it rebuilds, and a WidthFn or HeightFn lays out, in the next frame. Frames that only scroll or animate transforms do not check it. The same Value is returned on every build.

func Tabs() tela.Node {
    tab := tela.UseElementRef()
    size := tela.UseSize(tab)
    return Div(Class("relative flex"),
        Button(Ref(tab), Class("px-4 py-2"), Text("Overview")),
        Div(Class("absolute bottom-0 h-0.5 bg-indigo-500"),
            Style(tela.WidthFn(func() float32 { return size.Get().Width }))))
}

Example

t := &exampleT{}
defer t.close()
var size *tela.Value[tela.Size]
feed := func() tela.Node {
    card := tela.UseElementRef()
    size = tela.UseSize(card)
    s := size.Get() // a build reads it: the label follows the next frame
    return Div(TestID("feed"), Class("h-40 overflow-y-auto"),
        Div(Ref(card), Class("h-24 w-64 scale-150"), Textf("%.0f x %.0f", s.Width, s.Height)),
        Div(Class("h-96")),
    )
}
app := telatest.Mount(t, tela.C(feed))
app.ExpectText("256 x 96") // the layout size: the scale does not count
app.Scroll(telatest.ByTestID("feed"), 0, 80)
fmt.Println(size.Get()) // scrolling does not change it either
// Output: {256 96}

func UseSpring

func UseSpring[T Animatable](target T, opts ...AnimationOption) *Value[T]

UseSpring returns a Value that follows target with a spring: on the first build it holds the Initial option's value (target itself when there is none, so nothing moves) and, if target differs, starts moving to it; whenever a build passes a target that differs from the previous one, the spring retargets from where the value is, keeping its velocity, so an interrupted motion turns smoothly instead of stopping (Compose's animate*AsState, Motion's useSpring). T is the type of target, so a float32 target needs a typed value (float32(0), a float32 variable). It is a Value like UseValue's: read it in a deferred style closure (TranslateFn, OpacityFn, WidthFn...) and the element animates by repainting or laying out only, without rebuilding the component; the same *Value is returned on every build.

The options are [AnimationOption]s: Stiffness and Damping, or Duration and Bounce; without them, Motion's default spring (stiffness 100, damping 10). The spring ends when it comes to rest (its speed and its distance to the target are below Motion's thresholds), at exactly the target, and from then on the app requests no frames.

A panel that slides in when open changes, and in from the left when it mounts open:

target := float32(-320)
if open {
    target = 0
}
x := tela.UseSpring(target, tela.Initial(float32(-320)), tela.Stiffness(300), tela.Damping(30))
return Div(Class("fixed inset-y-0 left-0 w-80 bg-white shadow-xl"),
    Style(tela.TranslateFn(func() (float32, float32) { return x.Get(), 0 })))

With reduced motion (App, or the system setting), or in a tree that no frame clock drives (telatest.Render, tela snapshot), the value jumps to each new target. Calling Set on the Value stops the spring; the next target change starts from the value set.

Example

t := &exampleT{}
defer t.close()
var x *tela.Value[float32]
panel := func() tela.Node {
    open, setOpen := tela.UseState(false)
    target := float32(-320)
    if open {
        target = 0
    }
    x = tela.UseSpring(target, tela.Stiffness(300), tela.Damping(30))
    return Div(Class("relative h-full"),
        Button(OnClick(func(tela.MouseEvent) { setOpen.Set(true) }), Text("Open")),
        Div(Class("absolute inset-y-0 left-0 w-80 bg-white shadow-xl"),
            Style(tela.TranslateFn(func() (float32, float32) { return x.Get(), 0 }))))
}
// NoSettle: the click does not run the spring to its end.
app := telatest.Mount(t, tela.C(panel), telatest.NoSettle())
app.Click(telatest.ByRole("button", "Open"))
for range 3 {
    app.Advance(100 * time.Millisecond)
    fmt.Printf("%.1f\n", x.Get())
}
app.Advance(time.Second) // Advance moves the clock: the spring comes to rest
fmt.Println(x.Get())
// Output:
// -140.5
// -24.7
// -0.1
// 0

func UseTween

func UseTween[T Animatable](target T, opts ...AnimationOption) *Value[T]

UseTween is UseSpring with a tween: the value moves to each new target in a fixed Duration along an Ease curve (default 300 ms, CSS's "ease"), like a CSS transition of the value. A new target starts a new tween from where the value is (tweens have no velocity to keep). Like UseSpring, it starts at the Initial option's value, or at target.

op := tela.UseTween(target, tela.Duration(200*time.Millisecond), tela.Ease("ease-out"))
Div(Class("rounded-lg bg-slate-900 p-3 text-white"),
    Style(tela.OpacityFn(op.Get)), Text("Saved"))

It takes only Duration, Ease, Initial and OnDone; spring options panic in dev mode.

Example

t := &exampleT{}
defer t.close()
var op *tela.Value[float32]
toast := func() tela.Node {
    shown, setShown := tela.UseState(false)
    target := float32(0)
    if shown {
        target = 1
    }
    op = tela.UseTween(target, tela.Duration(200*time.Millisecond), tela.Ease("linear"))
    return Div(
        Button(OnClick(func(tela.MouseEvent) { setShown.Set(true) }), Text("Save")),
        Div(Class("rounded-lg bg-slate-900 p-3 text-white"), Style(tela.OpacityFn(op.Get)), Text("Saved")))
}
app := telatest.Mount(t, tela.C(toast), telatest.NoSettle())
app.Click(telatest.ByRole("button", "Save"))
app.Advance(50 * time.Millisecond)
fmt.Printf("%.2f\n", op.Get())
app.Advance(150 * time.Millisecond)
fmt.Printf("%.2f\n", op.Get())
// Output:
// 0.25
// 1.00

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, BgFn, TextColorFn): 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(url string) tela.Node {
    progress := tela.UseValue(0.0) // from 0 to 1
    tela.UseGo(func(ctx context.Context) {
        fetch(ctx, url, func(done float64) { progress.Set(done) }) // any goroutine
    }, url)
    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.

Because the *Value is stable and comparable, a struct of Values is a good return value for a custom hook: the caller keeps the struct between builds, passes it in CP props or a context without defeating skipping, and each reader redoes only what it read.

type Drag struct{ X, Y *tela.Value[float32] }

func UseDrag() Drag {
    return Drag{X: tela.UseValue(float32(0)), Y: tela.UseValue(float32(0))}
}

In handlers, effects and goroutines, read a Value that may have queued changes with Latest: Get returns the value applied last, and dev mode warns about a Get that misses queued changes.

func (*Value) Get

func (v *Value[T]) Get() T

Get returns the value. On the UI thread, while a component builds or a deferred style closure is evaluated in layout or paint, the read subscribes what is being evaluated: when the value changes, that component is built again, or that box laid out or repainted, and nothing else. Elsewhere (event handlers, effects) nothing is subscribed.

Get is safe from any goroutine. It returns the value the UI thread applied last: Set and Update calls still waiting in the queue are not visible until the UI thread applies them (on its next wake or frame). In dev mode, a Get outside a build or a deferred closure (a handler, an effect, a goroutine) while changes are queued is reported once per call site, with Latest as the fix (ADR-085).

func (*Value) Latest

func (v *Value[T]) Latest() T

Latest returns the value as it will be once the UI thread applies the Set and Update calls queued so far: the applied value (what Get returns) with every pending operation applied to it, in order. Use it in code that reads a Value it just changed, or that another goroutine is changing, before the next frame applies the change:

pos.Set(x)
pos.Latest() // x, while pos.Get() is still the old value

An Update function runs again here, on the calling goroutine, which is why it must be pure (and must not call Latest of its own Value). Latest is safe from any goroutine and never subscribes what is being evaluated, so it does not rebuild or relayout anything when the value changes; reading a Value in a build or in a deferred style closure is what Get is for. A change that has not been queued yet (a Set on another goroutine that has not returned) is not seen; one made through UseScroll or UseBounds is applied by the UI thread directly and is seen by Get as soon as it is made.

func (*Value) Set

func (v *Value[T]) Set(x T)

Set queues setting the value to x. Safe from any goroutine; the UI thread applies queued updates in order, with the state updates. For comparable types, a value == to the current one when the update applies changes nothing.

func (*Value) Update

func (v *Value[T]) Update(f func(T) T)

Update queues f, which receives the value as left by the updates queued before it and returns the new one: two queued Update(inc) add 2. f runs on the UI thread when the update applies (and in Latest, on the caller's goroutine) and must be pure.

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) })
    }
}))

WheelEvent is an alias of core.WheelEvent, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type WheelEvent struct {
    // X and Y are the pointer position in the window, LocalX and LocalY
    // relative to the border box of the element whose handler runs (see
    // MouseEvent).
    X, Y, LocalX, LocalY float32
    // DeltaX and DeltaY are the scroll distance in logical pixels, with
    // the DOM's signs: positive DeltaY scrolls down (toward the end of the
    // content), positive DeltaX to the right. A wheel notch is 53 pixels;
    // Shift turns a notch of a vertical wheel into a horizontal one.
    DeltaX, DeltaY float32
    // Modifier keys held during the event.
    Shift, Ctrl, Alt, Meta bool
    // contains filtered or unexported fields
}

func (WheelEvent) CmdOrCtrl

func (e WheelEvent) CmdOrCtrl() bool

CmdOrCtrl reports whether the primary modifier of shortcuts is held (see MouseEvent.CmdOrCtrl).

func (WheelEvent) DefaultPrevented

func (e WheelEvent) DefaultPrevented() bool

DefaultPrevented reports whether a handler called PreventDefault.

func (WheelEvent) PreventDefault

func (e WheelEvent) PreventDefault()

PreventDefault cancels the scroll the event would cause.

func (WheelEvent) StopPropagation

func (e WheelEvent) StopPropagation()

StopPropagation stops the event from bubbling (see MouseEvent).

type Window

type Window struct {
    // Title is the window title.
    Title string
    // Width and Height are the size of the content area in logical
    // pixels. Zero means the platform's default size (800×600); set both
    // or neither.
    Width, Height float32
    // MinWidth, MinHeight, MaxWidth and MaxHeight limit the size the
    // user can give the window; zero is no limit on that side.
    MinWidth, MinHeight, MaxWidth, MaxHeight float32
    // FixedSize keeps the user from resizing the window.
    FixedSize bool
    // Frameless removes the OS title bar and borders: the content draws
    // its own title bar, marked with h.WindowDrag and h.WindowControl.
    // The window stays resizable at its edges (unless FixedSize).
    Frameless bool
    // AlwaysOnTop keeps the window above other windows (not on Wayland,
    // which has no way to ask for it).
    AlwaysOnTop bool
    // Modal makes the window modal to the window it is declared in: that
    // window takes no input while this one is visible (a press there
    // brings this one forward).
    Modal bool
    // Hidden hides the window: it stays open, with its state, until
    // Hidden is false again. A hidden window draws nothing, but its
    // components build and its effects run as in a shown one.
    Hidden bool
    // Icon is the window's icon (title bar, task bar, alt-tab), the same
    // picture at several sizes (16 to 256 pixels; the OS picks). Empty is
    // the app's default icon.
    Icon []image.Image
    // TitleBar sets the colors of the native title bar and border on
    // Windows 11; see [TitleBarColors]. The zero value keeps the
    // system's. Other platforms ignore it.
    TitleBar TitleBarColors
    // Backdrop is the system material behind the window (Mica, Mica Alt,
    // Acrylic) on Windows 11 22H2; see [Backdrop]. The zero value is the
    // system's choice. Other platforms ignore it.
    Backdrop Backdrop
    // RootClass are Tailwind classes for the window's root, which fills
    // the window, as App.RootClass for the main window ("bg-background").
    RootClass string
    // Placement is where the window opens: a WindowPlacement saved from
    // [WindowHandle].Placement, so the window comes back where the user
    // left it. Its size, when set, wins over Width and Height. The zero
    // value lets the OS place the window.
    Placement WindowPlacement
    // OnClose runs when the user asks to close the window. Update the
    // state that renders this Window there.
    OnClose func()
    // Root is the content of the window: a struct component value
    // (Root: Settings{}) or a function component made with C.
    Root Component
}

Window is a component that opens a window while it is mounted and closes it when it unmounts: whether a window is open is state, like whether a dialog is shown. It renders nothing where it is declared; its Root is the content of the new window.

func Notes() tela.Node {
    settingsOpen, setSettingsOpen := tela.UseState(false)
    return Div(
        Button(OnClick(func() { setSettingsOpen.Set(true) }), Text("Settings")),
        If(settingsOpen, tela.Component(tela.Window{
            Title:   "Settings",
            Width:   480, Height: 360,
            Modal:   true,
            OnClose: func() { setSettingsOpen.Set(false) },
            Root:    SettingsPanel{},
        })),
    )
}

The window belongs to the window it is declared in: it stays above it, and with Modal that window takes no input while this one is visible. The app has one tree of components across its windows: Root is built by the component that declares the Window, so it sees that component's state and props, and the contexts above it reach the new window (UseContext); a Value read in the new window rebuilds it when it changes. Each window has its own layout, focus, hover, overlays (h.Portal), cursor and scale; the theme, the class aliases and the color scheme are the app's.

When the user asks to close the window (its close button, Alt+F4, a WindowClose control, WindowHandle.Close), OnClose runs: it should update the state that renders the Window, which unmounts it and closes the window. Without OnClose the request does nothing and the window stays open (dev mode warns once). To keep the window and its state while it is out of sight, set Hidden instead of unmounting it.

The fields are applied when they change: a new Title, Width and Height, size limits, AlwaysOnTop, Hidden, Icon, TitleBar, Backdrop or RootClass update the open window. FixedSize, Frameless, Modal and Placement are read when it opens (give the Window a new key to open it again with others).

tela snapshot, tela inspect and tela mcp show the main window only; in tests, telatest's App.Windows and App.Window reach the others. Under tela dev the other windows keep their parent, modality and window control, and an open window survives a reload (it is keyed by its place in the tree).

func (Window) Build

func (w Window) Build() Node

Build opens the window while the component is mounted (see Window).

type WindowControl

type WindowControl = core.WindowControl

WindowControl is what a window-control button of a custom title bar does, given to h.WindowControl.

WindowControl is an alias of core.WindowControl, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type WindowControl uint8
const (
    // WindowMinimize minimizes the window.
    WindowMinimize WindowControl = iota + 1
    // WindowMaximize maximizes the window, or restores a maximized one.
    WindowMaximize
    // WindowClose asks to close the window, as the OS's close button
    // does: the window's OnClose runs (for the main window, App.OnClose,
    // or the app quits).
    WindowClose
)

func (WindowControl) String

func (c WindowControl) String() string

type WindowHandle

type WindowHandle = core.WindowHandle

WindowHandle is a window of the app: the one a component renders in (UseWindow), or the main window given to App.OnClose. It is a small comparable value, the same for the window's whole life, with these methods:

  • Minimize, Maximize, Unmaximize, ToggleMaximize, SetFullscreen(bool): ask the OS to change the window's state.
  • Hide and Show: hide the window (it stays open, with its state) and show it again (activated). The app keeps running while its windows are hidden: App.OnClose can hide the main window to keep the app in the tray. A hidden or minimized window draws nothing, but its components build, its effects run and its Windows open as usual (a tray menu declared in it follows its state); it draws the current state when it shows.
  • Focus: raise the window and give it the keyboard focus, bringing it back from minimized.
  • Close: ask to close the window as its close button does (its OnClose runs; for the main window App.OnClose, or the app quits).
  • State() WindowState: maximized, fullscreen, minimized, focused, hidden and whether the app draws the title bar (ClientSideDecorations). Read in a component's body, it rebuilds the component when the state changes.
  • Placement() WindowPlacement: where the window is, to save and give back as App.Placement or Window.Placement next time.
  • IsZero() bool: the zero handle (no window).

The requests are safe from any goroutine: they are made on the UI thread, in order with the state updates. The OS may refuse one (a tiling window manager does not maximize); State says what happened. The handle of a closed window, or one in tela snapshot (no window), does nothing.

WindowHandle is an alias of core.WindowHandle, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type WindowHandle struct {
    // contains filtered or unexported fields
}

func UseWindow

func UseWindow() WindowHandle

UseWindow returns the handle of the window the component renders in: the main window, or the Window it is inside of.

func titleBar() tela.Node {
    win := tela.UseWindow()
    maximize := "Maximize"
    if win.State().Maximized { // the component rebuilds when it changes
        maximize = "Restore"
    }
    return Div(Class("flex h-9 items-center"), WindowDrag(),
        Span(Class("grow px-3 text-sm"), Text("Notes")),
        Button(WindowControl(tela.WindowMinimize), AriaLabel("Minimize"), Text("–")),
        Button(WindowControl(tela.WindowMaximize), AriaLabel(maximize), Text("□")),
        Button(WindowControl(tela.WindowClose), AriaLabel("Close"), Text("×")),
    )
}

func (WindowHandle) Close

func (w WindowHandle) Close()

Close asks to close the window, as the user does with the title bar's close button: the window's OnClose runs (it removes the Window from the state that renders it); for the main window App.OnClose runs, or the app quits.

func (WindowHandle) Focus

func (w WindowHandle) Focus()

Focus asks the OS to raise the window and give it the keyboard focus, restoring it if it is minimized. The OS may refuse (focus stealing prevention) and flash the window instead.

func (WindowHandle) Hide

func (w WindowHandle) Hide()

Hide hides the window. It stays open (mounted, with its state and effects) until it is shown again or closed; the app keeps running while its windows are hidden ("close to tray"). A hidden window draws nothing, but its components still build and its effects run; it draws the current state when it is shown.

func (WindowHandle) IsZero

func (w WindowHandle) IsZero() bool

IsZero reports whether w is the zero WindowHandle (no window).

func (WindowHandle) Maximize

func (w WindowHandle) Maximize()

Maximize asks the OS to maximize the window.

func (WindowHandle) Minimize

func (w WindowHandle) Minimize()

Minimize asks the OS to minimize the window; Focus brings it back.

func (WindowHandle) Placement

func (w WindowHandle) Placement() WindowPlacement

Placement returns where the window is (WindowPlacement): what an app saves to open the window there next time. It is kept after the window closes. Safe from any goroutine.

func (WindowHandle) SetFullscreen

func (w WindowHandle) SetFullscreen(fullscreen bool)

SetFullscreen asks the OS to make the window fullscreen or to leave fullscreen.

func (WindowHandle) Show

func (w WindowHandle) Show()

Show shows a hidden window and activates it.

func (WindowHandle) State

func (w WindowHandle) State() WindowState

State returns the window's state. Read in a component's build, it subscribes the component: it builds again when the state changes. Elsewhere (handlers, effects, goroutines) it only reads it.

func (WindowHandle) ToggleMaximize

func (w WindowHandle) ToggleMaximize()

ToggleMaximize maximizes the window, or restores it when it is maximized (what a title bar's maximize button does).

func (WindowHandle) Unmaximize

func (w WindowHandle) Unmaximize()

Unmaximize asks the OS to restore a maximized window to its size.

type WindowPlacement

type WindowPlacement = core.WindowPlacement

WindowPlacement is where a window is: Width and Height (its logical size outside maximized and fullscreen), X and Y (the position in physical pixels of the desktop, when HasPosition: never on Wayland), Maximized, Fullscreen and RestoreToken (the OS's session restore token; on Wayland it lets the compositor put the window back where supported). Save it (WindowHandle.Placement) and give it back as App.Placement or Window.Placement: the window opens where the user left it. The zero value means nothing is known.

WindowPlacement is an alias of core.WindowPlacement, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type WindowPlacement struct {
    // Width and Height are the logical size of the content area outside
    // maximized and fullscreen (the size it returns to); zero is
    // unknown.
    Width, Height float32
    // X and Y are the top-left of the content area in physical pixels of
    // the OS's desktop, when HasPosition is set (never on Wayland, which
    // does not let apps place windows; RestoreToken does it there where
    // the compositor supports it).
    X, Y        int
    HasPosition bool
    // Maximized and Fullscreen are the state to open in.
    Maximized, Fullscreen bool
    // RestoreToken is an opaque token the OS uses to put the window back
    // ("" where none is needed).
    RestoreToken string
}

type WindowState

type WindowState = core.WindowState

WindowState is the state of a window (WindowHandle.State): Maximized, Fullscreen and Minimized as the OS reports them (Wayland cannot report minimized windows), Focused (the active window), Hidden (the app hid it) and ClientSideDecorations (the OS draws no title bar: the app draws one, as a frameless window does, and so on a Wayland desktop without server-side decorations).

WindowState is an alias of core.WindowState, declared in an internal package of the module. pkg.go.dev shows only the alias; the exported fields and methods of the aliased type, listed here, are part of the API:

type WindowState struct {
    // Maximized, Fullscreen and Minimized are the window's OS state as
    // the OS reported it (Wayland cannot report minimized windows).
    Maximized, Fullscreen, Minimized bool
    // Focused: the window has the keyboard focus (it is the active
    // window).
    Focused bool
    // Hidden: the app hid the window (Hide, or Hidden).
    Hidden bool
    // ClientSideDecorations: the OS draws no title bar or borders for the
    // window (a Frameless window, or a Wayland compositor without
    // server-side decorations), so the app draws its own title bar.
    ClientSideDecorations bool
}

Clone this wiki locally