Documentation
¶
Overview ¶
Package tela is the public API of Tela, a desktop UI framework written in pure Go: no cgo, no webview, a React-like component model and Tailwind CSS styling.
A UI is a tree of Node values built by 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), 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 tela.Component), 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.
Index ¶
- Constants
- Variables
- func Animate[T Animatable](v *Value[T], target T, opts ...AnimationOption)
- func Assets(fsys fs.FS)
- func DevMode() bool
- func Main(app App)
- func MapValue[T, U any](v *Value[T], f func(T) U) func() U
- func OpenPath(path string) error
- func OpenURL(url string) error
- func Post(fn func())
- func Quit()
- func Run(app App) error
- func SetColorScheme(s ColorScheme)
- func SetTheme(t Theme)
- func Toggle(set Setter[bool]) func()
- func UseContext[T any](ctx *Context[T]) T
- func UseDirection() string
- func UseEffect(effect func() func(), deps ...any)
- func UseEffectDo(f func(), deps ...any)
- func UseGo(f func(ctx context.Context), deps ...any)
- func UseID() string
- func UseInterval(d time.Duration, f func())
- func UseLayoutEffect(effect func() func(), deps ...any)
- func UseMemo[T any](compute func() T, deps ...any) T
- func UseMount(effect func() func())
- func UseOnce[T any](init func() T) T
- func UsePresence() (present bool, done func())
- func UseReducedMotion() bool
- func UseStateSet[T any](initial T) (T, func(T))
- func UseStateUpdate[T any](initial T) (T, func(func(T) T))
- func UseTimeout(d time.Duration, f func(), deps ...any)
- func UseUpdateEffect(effect func() func(), deps ...any)
- type Animatable
- type AnimationOption
- func Bounce(b float64) AnimationOption
- func Damping(c float64) AnimationOption
- func Duration(d time.Duration) AnimationOption
- func Ease(e Easing) AnimationOption
- func Initial[T Animatable](v T) AnimationOption
- func OnDone(fn func()) AnimationOption
- func Spring(opts ...AnimationOption) AnimationOption
- func Stiffness(k float64) AnimationOption
- func Velocity[T Animatable](v T) AnimationOption
- type App
- type Backdrop
- type Callback
- type ChangeEvent
- type Child
- type Color
- type ColorScheme
- type Component
- type CompositionEvent
- type Context
- type DevState
- type Dispatcher
- type DragEvent
- type Easing
- type ElementRef
- type FocusEvent
- type Gradient
- type GradientStop
- type Icon
- type InputEvent
- type KeyCode
- type KeyLocation
- type KeyName
- type KeyboardEvent
- type MouseButton
- type MouseButtons
- type MouseEvent
- type Node
- type Point
- type PointerEvent
- type PointerKind
- type Preview
- type Previews
- type Prop
- type Rect
- type Ref
- type SchemeColor
- type ScrollAlign
- type ScrollEvent
- type SelectEvent
- type Setter
- type Shadow
- type Size
- type StopHandler
- type StyleProp
- func BackgroundGradient(g Gradient) StyleProp
- func Bg(c Color) StyleProp
- func BgFn(fn func() Color) StyleProp
- func BgImage(img image.Image) StyleProp
- func BorderColor(c Color) StyleProp
- func BorderWidth(px float32) StyleProp
- func Bottom(px float32) StyleProp
- func Box(x, y, w, h float32) StyleProp
- func BoxShadow(layers ...Shadow) StyleProp
- func FontSize(px float32) StyleProp
- func FontWeight(w float32) StyleProp
- func Gap(px float32) StyleProp
- func Height(px float32) StyleProp
- func HeightFn(fn func() float32) StyleProp
- func Left(px float32) StyleProp
- func Margin(px float32) StyleProp
- func MaxHeight(px float32) StyleProp
- func MaxWidth(px float32) StyleProp
- func MinHeight(px float32) StyleProp
- func MinWidth(px float32) StyleProp
- func Opacity(v float32) StyleProp
- func OpacityFn(fn func() float32) StyleProp
- func Padding(px float32) StyleProp
- func PaddingXY(x, y float32) StyleProp
- func Radius(px float32) StyleProp
- func Right(px float32) StyleProp
- func Rotate(deg float32) StyleProp
- func Scale(x, y float32) StyleProp
- func TextColor(c Color) StyleProp
- func TextColorFn(fn func() Color) StyleProp
- func Top(px float32) StyleProp
- func Translate(x, y float32) StyleProp
- func TranslateFn(fn func() (x, y float32)) StyleProp
- func WH(w, h float32) StyleProp
- func Width(px float32) StyleProp
- func WidthFn(fn func() float32) StyleProp
- func ZIndex(z int) StyleProp
- type SubmitEvent
- type Theme
- type ThemeValues
- type TitleBarColors
- type Value
- func UseBounds(ref ElementRef) *Value[Rect]
- func UsePresenceTween(opts ...AnimationOption) *Value[float32]
- func UseScroll(ref ElementRef) (x, y *Value[float32])
- func UseSize(ref ElementRef) *Value[Size]
- func UseSpring[T Animatable](target T, opts ...AnimationOption) *Value[T]
- func UseTween[T Animatable](target T, opts ...AnimationOption) *Value[T]
- func UseValue[T any](initial T) *Value[T]
- type WheelEvent
- type Window
- type WindowControl
- type WindowHandle
- type WindowPlacement
- type WindowState
Examples ¶
- Animatable
- Animate
- AnimationOption
- Assets
- BgFn
- BgImage
- Bounce
- Box
- CP (Key)
- Component
- Component (Skipping)
- CubicBezier
- Damping
- DevState
- Duration
- Ease
- Easing
- Hex
- Main
- MapValue
- MergeProps
- OnDone
- Point
- Preview
- Previews
- SetColorScheme
- Size
- Spring
- Stiffness
- StyleProp
- TextColorFn
- Toggle
- UseColorScheme
- UseDirection
- UseEffectDo
- UseGo
- UseInterval
- UseMount
- UseOnce
- UsePresence
- UsePresenceTween
- UseReducedMotion
- UseSize
- UseSpring
- UseTimeout
- UseTween
- UseUpdateEffect
- Velocity
- WH
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 ( // 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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶
package main
import (
"bytes"
"fmt"
"image"
"image/color"
"image/png"
"testing/fstest"
"github.com/kuronosu/tela"
"github.com/kuronosu/tela/internal/core"
"github.com/kuronosu/tela/internal/style"
)
// solidPNG returns a 2x2 PNG of one color.
func solidPNG(c color.Color) []byte {
img := image.NewRGBA(image.Rect(0, 0, 2, 2))
for i := range 4 {
img.Set(i%2, i/2, c)
}
var buf bytes.Buffer
png.Encode(&buf, img)
return buf.Bytes()
}
func main() {
// 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 ¶ added in v0.10.0
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶ added in v0.12.0
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 ¶ added in v0.12.0
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 ¶ added in v0.12.0
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 ¶
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 and Cocoa elsewhere; 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 ¶ added in v0.6.0
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶
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 ¶ added in v0.10.0
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 ¶
package main
import (
"fmt"
"github.com/kuronosu/tela"
"github.com/kuronosu/tela/h"
"github.com/kuronosu/tela/telatest"
)
func main() {
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 UseID ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶
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 ¶ added in v0.7.0
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 UseOnce ¶ added in v0.7.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 UseTimeout ¶ added in v0.11.0
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 ¶ added in v0.7.0
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 ¶ added in v0.6.0
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.
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 ¶ added in v0.6.0
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.
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶ added in v0.7.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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
// 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 ¶ added in v0.12.0
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.
type Callback ¶
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()()
}
func UseCallback ¶
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.
type ChangeEvent ¶
type ChangeEvent = core.ChangeEvent
ChangeEvent is passed to h.OnChange after every edit of a text field's value, like React's onChange (the DOM's input event, not its change on blur): Value is the new value (with the text an IME is composing), InputType, Data and IsComposing describe the edit as in InputEvent. It bubbles, so a Form's OnChange sees the edits of all its fields.
name, setName := tela.UseState("")
Input(Value(name), OnChange(func(e tela.ChangeEvent) { setName.Set(e.Value) }))
// the same: Input(Bind(name, setName))
type Child ¶
Child is what an element takes: a Node (a child), a Prop (h.Class, h.Style, h.Key, h.OnClick...) or any other Component, which becomes a component with its own state. Props and children may be mixed in any order. It has the method set of Component but is a type of its own: a field or parameter of type Child takes props too, one of type Component is for what renders. 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")).
type Color ¶
Color is a non-premultiplied color with float32 components R, G, B and A. A (opacity) is in [0, 1]. R, G and B are extended sRGB, like CSS Color 4: they are in [0, 1] for colors inside the sRGB gamut, while a color outside it, such as a third of the Tailwind palette once converted from OKLCH, keeps components below 0 or above 1 and is clipped when painted, as browsers do. Clamp before converting to 8-bit values. The zero value is transparent black.
Color is an alias of an internal type: use its fields and methods (WithAlpha, MulAlpha, String...) and the constructors Hex, RGB, RGBA and OKLCH.
func Hex ¶
Hex parses a CSS hex color: "#rgb", "#rgba", "#rrggbb" or "#rrggbbaa". It panics with the reason when s is not valid, since a literal that does not parse is a programmer error.
tela.Hex("#1e1e2e")
Example ¶
package main
import (
"fmt"
"github.com/kuronosu/tela"
)
func main() {
c := tela.Hex("#1e1e2e")
fmt.Println(c.R == tela.RGB(30, 30, 46).R, c)
}
Output: true #1e1e2e
func OKLCH ¶
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).
type ColorScheme ¶ added in v0.6.0
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.
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 ¶ added in v0.6.0
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"
type Component ¶ added in v0.7.0
Component is a unit of UI with its own state boundary: a value whose Build method returns what it renders. Build must use a value receiver.
type Card struct {
Title string
OnOpen tela.Callback[func()]
Children tela.Component // 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))
}
Div(Card{Title: "Inbox", Children: Fragment(Text("a"), Span(Text("b")))})
Every Component value given to an element as a child 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. 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. A Node in a field compares by identity: a fresh h.Div(...) 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 pass Card{...} as a child instead. Where a Node is expected (the result of a function component, an h.Map item), give the value to h.Fragment, or key it with WithKey. h.If takes a struct component as is, If(loading, Spinner{}), and renders nothing when the condition is false; it returns a Child, so a function that returns a Node wraps it: Fragment(If(...)).
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 Component (Card{Children: Class("p-4")}): Go cannot tell it from a component 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.
Node and Prop implement Component as well; a function component becomes one with C, CP or Memo.
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.
type Context ¶
Context passes a value down the tree without props: see CreateContext.
func CreateContext ¶
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.
type DevState ¶ added in v0.9.0
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, ¤t) },
}
}
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).
package main
import (
"encoding/json"
"fmt"
"github.com/kuronosu/tela"
)
func main() {
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.
func UseReducer ¶
func UseReducer[S, A any](reducer func(S, A) S, initial S) (S, Dispatcher[A])
UseReducer returns the state of this hook slot and a Dispatcher whose actions update it with reducer (React's useReducer). The first build stores initial. Queued actions apply in order on the UI thread with the reducer of the latest build, each receiving the state left by the previous one; reducer must be pure. For comparable states, a result == to the current state does not rebuild.
type action struct{ kind string }
func reduce(n int, a action) int {
switch a.kind {
case "inc":
return n + 1
case "reset":
return 0
}
return n
}
count, d := tela.UseReducer(reduce, 0)
d.Dispatch(action{"inc"})
type DragEvent ¶
DragEvent is passed to h.OnDragStart, h.OnDrag and h.OnDragEnd: the position, the movement since the previous drag event (DeltaX, DeltaY) and since the press (OffsetX, OffsetY), and the velocity in pixels per second (VelocityX, VelocityY; meaningful at the end).
type Easing ¶ added in v0.7.0
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.
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
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.
func CubicBezier ¶ added in v0.7.0
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")))
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"))
}
type FocusEvent ¶
type FocusEvent = core.FocusEvent
FocusEvent is passed to h.OnFocus and h.OnBlur. Like React's onFocus and onBlur, focus events bubble: a container's OnFocus also runs when one of its descendants gains the focus.
Div(OnFocus(func(tela.FocusEvent) { setEditing.Set(true) }),
OnBlur(func(tela.FocusEvent) { setEditing.Set(false) }),
...)
type Gradient ¶
Gradient is a background gradient; build one with LinearGradient.
func LinearGradient ¶
func LinearGradient(angleDeg float32, stops ...GradientStop) Gradient
LinearGradient returns a linear gradient in the direction of angleDeg: 0 points up, 90 to the right and 180 (CSS's default) down. Colors interpolate in Oklab, like Tailwind v4.
type GradientStop ¶
GradientStop is a color stop of a gradient; build one with Stop or StopAt.
func Stop ¶
func Stop(c Color) GradientStop
Stop returns a gradient stop whose position is spread automatically between its neighbors, as in CSS.
func StopAt ¶
func StopAt(c Color, percent float32) GradientStop
StopAt returns a gradient stop at a percentage (0 to 100) of the gradient line.
type Icon ¶ added in v0.8.0
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 ¶ added in v0.8.0
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)
type InputEvent ¶
type InputEvent = core.InputEvent
InputEvent is passed to h.OnBeforeInput before a text field's value changes by an edit of the user (the DOM's beforeinput): InputType names the edit with the W3C Input Events names ("insertText", "insertFromPaste", "insertLineBreak", "deleteContentBackward", "deleteWordBackward", "deleteByCut", "historyUndo", "insertCompositionText"...), Data is the inserted text (empty for deletions), IsComposing reports an IME composition. PreventDefault cancels the edit, except during a composition and for the text an IME deletes around the caret. It bubbles.
// Digits only.
Input(OnBeforeInput(func(e tela.InputEvent) {
if strings.Trim(e.Data, "0123456789") != "" {
e.PreventDefault()
}
}))
type KeyCode ¶ added in v0.7.0
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() }
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.
type KeyName ¶ added in v0.7.0
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 == "+" { ... }
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). StopPropagation stops the bubbling; PreventDefault cancels the key's default action (Tab's focus move, the click of Enter or Space on a button, the text a key types into a text field). Typed text reaches text fields (h.Input, h.Textarea), which report it with h.OnChange.
OnKeyDown(func(e tela.KeyboardEvent) {
switch {
case e.Key == tela.KeyArrowDown:
e.PreventDefault()
setIndex.Update(func(i int) int { return min(i+1, n-1) })
case e.Ctrl && e.Key == "a":
selectAll()
}
})
type MouseButton ¶
type MouseButton = core.MouseButton
MouseButton is a mouse button, or the touch contact or pen tip that stands for the primary one.
type MouseButtons ¶
type MouseButtons = core.MouseButtons
MouseButtons is a set of held buttons: e.Buttons.Has(tela.MouseButtonLeft).
type MouseEvent ¶
type MouseEvent = core.MouseEvent
MouseEvent is passed to the mouse handlers (h.OnClick, h.OnMouseDown, h.OnContextMenu...). X and Y are window coordinates in logical pixels; LocalX and LocalY are relative to the border box of the element whose handler runs. ClickCount is 1 for a click, 2 for the second click of a double click. StopPropagation stops the bubbling; PreventDefault marks the event (on h.OnPointerDown it suppresses the press's mouse events, not the click).
type Node ¶
Node is an immutable, cheap description of a piece of UI: an element (h.Div), a text (h.Text), a component or a fragment. Function components return one, and every build creates new ones; the persistent state lives elsewhere, keyed by position and key (ARCHITECTURE section 6).
The zero Node renders nothing. As a child of an element it keeps the positions of its siblings, so h.If(cond, node) does not remount the siblings when cond changes. A Node is a Component whose Build returns the node itself.
func C ¶
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 ¶
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 Component, 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), which it also turns into a Node (for h.Map and other places that want one). 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 or the zero Node give the zero Node; a Prop panics.
Map(cards, func(c CardData) tela.Node {
return tela.WithKey(Card{Title: c.Title}, c.ID)
})
type Point ¶ added in v0.6.0
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.
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.
type Preview ¶ added in v0.7.0
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
type Previews ¶
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` (or tela.Component) instead. See Preview to start a screen in a given state.
Example ¶
package main
import (
"fmt"
"github.com/kuronosu/tela"
"github.com/kuronosu/tela/internal/core"
)
func main() {
login := func() tela.Node { return core.Text("Login") }
settings := func() tela.Node { return core.Text("Settings") }
previews := tela.Previews{
"Settings": {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]
type 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 Component, although it renders nothing: dev mode reports one held by a field of type Component).
func MergeProps ¶ added in v0.10.0
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 or another 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
type Rect ¶
Rect is a rectangle in logical pixels: the position of its top-left corner (X, Y) and its size (Width, Height). Right, Bottom and Contains derive from them. It is what ElementRef.Bounds and UseBounds return, in window coordinates (the window's top-left corner is 0, 0), like the DOM's DOMRect.
type Ref ¶
Ref is a mutable box returned by UseRef: the same pointer on every build. Changing Current does not rebuild.
func UseRef ¶
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 ¶ added in v0.7.0
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")}
type ScrollAlign ¶
type ScrollAlign = core.ScrollAlign
ScrollAlign is where ElementRef.ScrollToIndex puts an item of an h.VirtualList in its viewport (TanStack Virtual's align).
type ScrollEvent ¶
type ScrollEvent = core.ScrollEvent
ScrollEvent is passed to h.OnScroll after an element's scroll offsets changed, by the user or by code (the DOM's scroll event: at most once per frame, on the scrolling element only; it does not bubble). The fields are the DOM's scroll properties in logical pixels: ScrollLeft and ScrollTop (the offsets), ScrollWidth and ScrollHeight (the content size), ClientWidth and ClientHeight (the visible size).
Div(Class("h-96 overflow-y-auto"), OnScroll(func(e tela.ScrollEvent) {
if e.ScrollTop+e.ClientHeight >= e.ScrollHeight-200 {
loadMore()
}
}), rows)
type SelectEvent ¶
type SelectEvent = core.SelectEvent
SelectEvent is passed to h.OnSelect when the user selects text in a text field or code changes its selection: Start and End are the selection in UTF-8 byte offsets of the value. It bubbles.
type Setter ¶
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.
func UseState ¶
UseState returns the state of this hook slot and its Setter (ARCHITECTURE section 7). The first build stores initial; later builds return the latest value set. Setting a different value rebuilds the component in the next frame.
func Counter() tela.Node {
count, setCount := tela.UseState(0)
return Button(OnClick(func(tela.MouseEvent) { setCount.Set(count + 1) }),
Textf("Clicked %d times", count))
}
Use setCount.Update(func(n int) int { return n + 1 }) when the new state depends on the old one and several updates may be queued before the next build (from goroutines, or several events in one frame).
Hooks follow React's rules: call them at the top level of a component function, in the same order on every build, never inside conditions or loops. UseState panics when called outside a component build or when the call order changed since the previous build.
type Shadow ¶
Shadow is one box shadow layer: OffsetX, OffsetY, Blur and Spread in logical pixels, its Color, and Inset for an inner shadow.
type Size ¶ added in v0.6.0
Size is a width and a height in logical pixels. It is one of the Animatable types.
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 ¶ added in v0.8.0
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 ¶ added in v0.8.0
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 ¶
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.
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 ¶
BackgroundGradient sets a gradient as the background image, over the background color.
func BgFn ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶
package main
import (
"fmt"
"image"
"image/color"
"github.com/kuronosu/tela"
"github.com/kuronosu/tela/internal/core"
"github.com/kuronosu/tela/internal/style"
)
func main() {
// 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 ¶
BorderColor sets the color of the four borders.
func BorderWidth ¶
BorderWidth sets the width of the four borders in pixels.
func Box ¶ added in v0.7.0
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 FontWeight ¶
FontWeight sets the font weight, from 100 to 900.
func HeightFn ¶
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 OpacityFn ¶
OpacityFn defers Opacity to the paint phase: a change of the Values fn reads repaints the element, without layout.
func TextColorFn ¶ added in v0.6.0
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 TranslateFn ¶
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 ¶ added in v0.7.0
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}
type SubmitEvent ¶
type SubmitEvent = core.SubmitEvent
SubmitEvent is passed to h.OnSubmit when a h.Form is submitted: Enter in a single-line field of the form, or a click on a submit button inside it. Tela does not navigate; PreventDefault exists for familiarity.
Form(OnSubmit(func(e tela.SubmitEvent) {
e.PreventDefault()
login(email, password)
}), ...)
type Theme ¶ added in v0.7.0
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. Tokens cannot be used inside arbitrary values (bg-[linear-gradient(...)]): use the utilities (from-primary, shadow-primary).
func DefaultTheme ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
func (t ThemeValues) FontMono() string
FontMono returns the family of font-mono: the theme's FontMono, or the embedded "JetBrains Mono".
func (ThemeValues) FontSans ¶ added in v0.7.0
func (t ThemeValues) FontSans() string
FontSans returns the family of font-sans: the theme's FontSans, or the embedded "Inter".
func (ThemeValues) Lookup ¶ added in v0.7.0
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 ¶ added in v0.7.0
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 ¶ added in v0.7.0
func (t ThemeValues) Scheme() ColorScheme
Scheme returns the color scheme of the values, Light or Dark.
func (ThemeValues) Tokens ¶ added in v0.7.0
func (t ThemeValues) Tokens() []string
Tokens returns the names of the theme's color tokens, sorted.
type TitleBarColors ¶ added in v0.12.0
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.
type Value ¶
Value is an observable value held by one UseValue hook slot: v.Get() reads it, v.Set(x) and v.Update(f) change it. The *Value is the same pointer on every build, so it is comparable and can be passed in props compared by CP or through a context. Set and Update are safe from any goroutine: they queue the change, in order with the state updates, and the UI thread applies it before the next frame. Get is safe from any goroutine too and returns the value the UI thread applied last (a change still in the queue is not visible yet); Latest returns the value as it will be once the queued changes apply, which is what an event handler that has just called Set wants to read. Latest never subscribes anything.
What a change redoes depends on where Get was called; see UseValue. For comparable types, a value == to the current one changes nothing.
func UseBounds ¶
func UseBounds(ref ElementRef) *Value[Rect]
UseBounds returns a Value that follows the bounds of the element ref is attached to with h.Ref: its border box in window coordinates, as ElementRef.Bounds returns it (moved by scrolling and transforms, the zero Rect while the element is not mounted or displayed). It is a ResizeObserver that also sees the position: use it to size or place one element after another, or to anchor a floating element.
The Value is updated at the end of every frame, when the bounds changed since the last one, so a component that reads it rebuilds, and a deferred style closure that reads it lays out or repaints, in the frame after the change. Frames where nothing moved cost one comparison and request no further frame. The same Value is returned on every build.
func Meter() tela.Node {
track := tela.UseElementRef()
b := tela.UseBounds(track)
return Div(Ref(track), Class("h-2 rounded bg-slate-200"),
Div(Class("h-2 rounded bg-indigo-500"),
Style(tela.WidthFn(func() float32 { return b.Get().Width * 0.4 }))))
}
In an event handler, ElementRef.Bounds gives the same rectangle without a Value: a seek bar turns the pointer's x into a fraction with e.X-ref.Bounds().X over ref.Bounds().Width.
func UsePresenceTween ¶ added in v0.7.0
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 ¶ added in v0.8.0
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 ¶ added in v0.6.0
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 ¶ added in v0.6.0
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 ¶
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.
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) })
}
}))
type Window ¶ added in v0.12.0
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).
type WindowControl ¶ added in v0.12.0
type WindowControl = core.WindowControl
WindowControl is what a window-control button of a custom title bar does, given to h.WindowControl.
type WindowHandle ¶ added in v0.12.0
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.
func UseWindow ¶ added in v0.12.0
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("×")),
)
}
type WindowPlacement ¶ added in v0.12.0
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.
type WindowState ¶ added in v0.12.0
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).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package aria provides unstyled behavior hooks for building components: the interaction and accessibility that a button, a checkbox or a dialog needs, without a look.
|
Package aria provides unstyled behavior hooks for building components: the interaction and accessibility that a button, a checkbox or a dialog needs, without a look. |
|
cmd
|
|
|
tela
command
Command tela is the Tela command line tool.
|
Command tela is the Tela command line tool. |
|
tela/internal/term
Package term puts the terminal in a mode that delivers single key presses (tela dev's r, R and q) and restores it.
|
Package term puts the terminal in a mode that delivers single key presses (tela dev's r, R and q) and restores it. |
|
tela/internal/watch
Package watch reports changes of files under a set of directory trees, debounced into batches: the file watcher of tela dev (ADR-115).
|
Package watch reports changes of files under a set of directory trees, debounced into batches: the file watcher of tela dev (ADR-115). |
|
Package desktop gives a Tela app the services of the desktop it runs on (ADR-178, ADR-181): file and folder dialogs, message boxes, notifications, a tray icon, the app's own folders, a settings store and windows that open where the user left them.
|
Package desktop gives a Tela app the services of the desktop it runs on (ADR-178, ADR-181): file and folder dialogs, message boxes, notifications, a tray icon, the app's own folders, a settings store and windows that open where the user left them. |
|
examples
|
|
|
aria
command
Command aria is a "Command center" screen built only from the behavior hooks of package aria, the elements of package h and Tailwind classes with the theme tokens of shadcn/ui: no kit components and no hand-written key handling.
|
Command aria is a "Command center" screen built only from the behavior hooks of package aria, the elements of package h and Tailwind classes with the theme tokens of shadcn/ui: no kit components and no hand-written key handling. |
|
board
command
Command board is a small kanban board built on package kit in its default style: three columns of cards that can be dragged from one column to another, or reordered inside a column, and moved, edited or deleted from a menu.
|
Command board is a small kanban board built on package kit in its default style: three columns of cards that can be dragged from one column to another, or reordered inside a column, and moved, edited or deleted from a menu. |
|
chat
command
Command chat is a messaging app screen built on package kit in its default style: a sidebar with a search box and the list of conversations, and next to it the open conversation with its header, its messages and a composer.
|
Command chat is a messaging app screen built on package kit in its default style: a sidebar with a search box and the list of conversations, and next to it the open conversation with its header, its messages and a composer. |
|
counter
command
Command counter is the Counter example of ARCHITECTURE section 1: a card with a heading and a button, styled only with Tailwind classes.
|
Command counter is the Counter example of ARCHITECTURE section 1: a card with a heading and a button, styled only with Tailwind classes. |
|
dashboard
command
Command dashboard is an analytics dashboard in the glassmorphism style: frosted panels over a colorful backdrop, in light and dark.
|
Command dashboard is an analytics dashboard in the glassmorphism style: frosted panels over a colorful backdrop, in light and dark. |
|
hello
command
Command hello is the smallest Tela program that looks like something: a static screen with a headline, a tagline and a rule, styled only with Tailwind classes.
|
Command hello is the smallest Tela program that looks like something: a static screen with a headline, a tagline and a rule, styled only with Tailwind classes. |
|
i18n
command
Command i18n is a translated screen built on package kit: an order summary in English, Spanish and Arabic that switches language while it runs, and lays itself out right to left in Arabic.
|
Command i18n is a translated screen built on package kit: an order summary in English, Spanish and Arabic that switches language while it runs, and lays itself out right to left in Arabic. |
|
kit
command
Command kit is the gallery of package kit: a sidebar with every component and, for each one, a page that shows its variants, sizes and states in a small realistic composition, as shadcn/ui's documentation does.
|
Command kit is the gallery of package kit: a sidebar with every component and, for each one, a page that shows its variants, sizes and states in a small realistic composition, as shadcn/ui's documentation does. |
|
layout
command
Command layout shows Tela's layout animations (ARCHITECTURE section 12, ADR-081): a task list whose rows slide to their new places when it is reordered (h.AnimateLayout), a tab bar whose underline slides from the tab that loses it to the one that gets it (h.LayoutID), and a card that grows when it is expanded while its header keeps its size and the note below it moves down.
|
Command layout shows Tela's layout animations (ARCHITECTURE section 12, ADR-081): a task list whose rows slide to their new places when it is reordered (h.AnimateLayout), a tab bar whose underline slides from the tab that loses it to the one that gets it (h.LayoutID), and a card that grows when it is expanded while its header keeps its size and the note below it moves down. |
|
llms/settings
command
Command settings is the worked example of docs/llms.txt, which shows this file verbatim: an app shell with a sidebar and a settings page on kit.
|
Command settings is the worked example of docs/llms.txt, which shows this file verbatim: an app shell with a sidebar and a settings page on kit. |
|
login
command
Command login is a "Welcome back" screen: a card that is a sign-in form on one side and a brand panel with a quote on the other, on package kit in its default style.
|
Command login is a "Welcome back" screen: a card that is a sign-in form on one side and a brand panel with a quote on the other, on package kit in its default style. |
|
motion
command
Command motion shows Tela's explicit animations (ARCHITECTURE section 12): a dialog that enters with the starting: variant and leaves with h.Exit inside h.Presence, a toast that runs its own exit with tela.UsePresenceTween, and a card that follows a drag through a spring (tela.Animate retargeted on every move, keeping its velocity) and springs back when it is released, with the pointer's release velocity (tela.Velocity).
|
Command motion shows Tela's explicit animations (ARCHITECTURE section 12): a dialog that enters with the starting: variant and leaves with h.Exit inside h.Presence, a toast that runs its own exit with tela.UsePresenceTween, and a card that follows a drag through a spring (tela.Animate retargeted on every move, keeping its velocity) and springs back when it is released, with the pointer's release velocity (tela.Velocity). |
|
native
command
Command native is a file explorer that looks native on Windows 11 (Fluent), GNOME (Adwaita) and KDE (Breeze), each in light and dark, built only on package kit.
|
Command native is a file explorer that looks native on Windows 11 (Fluent), GNOME (Adwaita) and KDE (Breeze), each in light and dark, built only on package kit. |
|
notes
command
Command notes is a small plain-text notes app that shows the desktop services of phase 12: package desktop (native open and save dialogs, a message box, notifications, a tray icon, the app's folders, settings and a remembered window position), two windows, and a custom title bar.
|
Command notes is a small plain-text notes app that shows the desktop services of phase 12: package desktop (native open and save dialogs, a message box, notifications, a tray icon, the app's folders, settings and a remembered window position), two windows, and a custom title bar. |
|
player
command
Command player is a music player built on package kit: a card with cover art, title, artist, a seek bar (kit.Slider), previous/play/next buttons (kit.Button with kit.Tooltip), a mute kit.Toggle with a volume kit.Slider, and a queue of tracks (kit.Card, a kit.Button per row).
|
Command player is a music player built on package kit: a card with cover art, title, artist, a seek bar (kit.Slider), previous/play/next buttons (kit.Button with kit.Tooltip), a mute kit.Toggle with a volume kit.Slider, and a queue of tracks (kit.Card, a kit.Button per row). |
|
pricing
command
Command pricing is a pricing page built on package kit in its default style: three plans as cards with a highlighted one, feature lists with check icons and a monthly/yearly segmented control that changes the prices.
|
Command pricing is a pricing page built on package kit in its default style: three plans as cards with a highlighted one, feature lists with check icons and a monthly/yearly segmented control that changes the prices. |
|
settings
command
Command settings is a preferences page built on package kit in its default style: a notifications card with switches, an appearance card with a radio group of theme cards and a segmented control, and a footer with Reset and Save buttons that stay disabled until something changed.
|
Command settings is a preferences page built on package kit in its default style: a notifications card with switches, an appearance card with a radio group of theme cards and a segmented control, and a footer with Reset and Save buttons that stay disabled until something changed. |
|
shapes
command
Command shapes is a showcase of what Tela's styling can paint: a page of twelve cards with shadows, gradients (linear, radial and conic), rings, blur and backdrop blur, opacity, rounded and rotated shapes, all written as elements and Tailwind classes.
|
Command shapes is a showcase of what Tela's styling can paint: a page of twelve cards with shadows, gradients (linear, radial and conic), rings, blur and backdrop blur, opacity, rounded and rotated shapes, all written as elements and Tailwind classes. |
|
signup
command
Command signup is a "Create your account" screen: a brand panel on the left and, on the right, a form built on package kit in its default style, with every kind of text field Tela has.
|
Command signup is a "Create your account" screen: a brand panel on the left and, on the right, a form built on package kit in its default style, with every kind of text field Tela has. |
|
text
command
Command text is the phase-2 showcase: mixed-script paragraphs (Latin, Arabic, CJK, emoji), a code block, truncation and decorations, laid out on a page of cards in a native window.
|
Command text is the phase-2 showcase: mixed-script paragraphs (Latin, Arabic, CJK, emoji), a code block, truncation and decorations, laid out on a page of cards in a native window. |
|
virtuallist
command
Command virtuallist is a directory of 100,000 people in an h.VirtualList: only the rows in view (plus a few) are built, laid out and painted, rows with a note are taller and measured when they appear, and the header's buttons jump to a row with ElementRef.ScrollToIndex.
|
Command virtuallist is a directory of 100,000 people in an h.VirtualList: only the rows in view (plus a few) are built, laid out and painted, rows with a note are taller and measured when they appear, and the header's buttons jump to a row with ElementRef.ScrollToIndex. |
|
Package h holds the elements, props and helpers of a Tela UI.
|
Package h holds the elements, props and helpers of a Tela UI. |
|
Package i18n translates an app and formats numbers, dates and lists for the user's locale, and switches the language at run time.
|
Package i18n translates an app and formats numbers, dates and lists for the user's locale, and switches the language at run time. |
|
internal/gen
command
Command gen generates i18n/dates_gen.go, the date, time and list patterns and the calendar data of package i18n, from the Unicode CLDR JSON data vendored in cldr/ (ADR-087, ADR-131).
|
Command gen generates i18n/dates_gen.go, the date, time and list patterns and the calendar data of package i18n, from the Unicode CLDR JSON data vendored in cldr/ (ADR-087, ADR-131). |
|
internal
|
|
|
anim
Package anim is Tela's animation layer (ARCHITECTURE sections 8 and 12): the pieces that turn time into values, without knowing the element tree.
|
Package anim is Tela's animation layer (ARCHITECTURE sections 8 and 12): the pieces that turn time into values, without knowing the element tree. |
|
assets/fonts
Package fonts embeds the fonts Tela ships with, as raw font file bytes.
|
Package fonts embeds the fonts Tela ships with, as raw font file bytes. |
|
core
Package core is Tela's runtime (ARCHITECTURE sections 6 to 8): the three trees, reconciliation, hooks, the frame pipeline and its scheduler, and the hosts that run a tree in a window or headless.
|
Package core is Tela's runtime (ARCHITECTURE sections 6 to 8): the three trees, reconciliation, hooks, the frame pipeline and its scheduler, and the hosts that run a tree in a window or headless. |
|
devloop
Package devloop holds what the tela dev supervisor (cmd/tela) and the child runtime (the //go:build teladev files of tela and internal/core) agree on: environment variables, the kinds of control messages they exchange over the remote backend's control channel (internal/platform/remote: Child.Request/Notify on the host, Backend.Request/Notify/Handle on the child), and the encodings of their payloads (ADR-111, ADR-115).
|
Package devloop holds what the tela dev supervisor (cmd/tela) and the child runtime (the //go:build teladev files of tela and internal/core) agree on: environment variables, the kinds of control messages they exchange over the remote backend's control channel (internal/platform/remote: Child.Request/Notify on the host, Backend.Request/Notify/Handle on the child), and the encodings of their payloads (ADR-111, ADR-115). |
|
editdist
Package editdist is the Levenshtein edit distance used by the "did you mean" suggestions of the layers above it (core, semantics, aria, telatest).
|
Package editdist is the Levenshtein edit distance used by the "did you mean" suggestions of the layers above it (core, semantics, aria, telatest). |
|
fmath
Package fmath holds the elementary functions that every layer feeding the rasterizer needs with the same result, bit for bit, on every architecture (ADR-027, ADR-041).
|
Package fmath holds the elementary functions that every layer feeding the rasterizer needs with the same result, bit for bit, on every architecture (ADR-027, ADR-041). |
|
gen/keys
command
Command keys generates keys_gen.go in package tela: a tela.KeyName constant for every named key of internal/platform (its Key constants) and a tela.KeyCode constant for every physical key code (its KeyCode constants and their W3C names), so the public lists never drift from the platform's.
|
Command keys generates keys_gen.go in package tela: a tela.KeyName constant for every named key of internal/platform (its Key constants) and a tela.KeyCode constant for every physical key code (its KeyCode constants and their W3C names), so the public lists never drift from the platform's. |
|
gen/llmsdoc
Package llmsdoc holds the tests that keep docs/llms.txt true to the code (ADR-177).
|
Package llmsdoc holds the tests that keep docs/llms.txt true to the code (ADR-177). |
|
gen/wikidoc
command
Command wikidoc generates the API reference pages of the GitHub wiki (docs/wiki/API-Reference-<pkg>.md) from the doc comments of Tela's public packages, so the wiki never drifts from pkg.go.dev.
|
Command wikidoc generates the API reference pages of the GitHub wiki (docs/wiki/API-Reference-<pkg>.md) from the doc comments of Tela's public packages, so the wiki never drifts from pkg.go.dev. |
|
gesture
Package gesture is Tela's gesture arena (ARCHITECTURE section 14), after Flutter's: the recognizers interested in a pointer sequence (a press and what follows until the release) compete in that pointer's arena, and one wins.
|
Package gesture is Tela's gesture arena (ARCHITECTURE section 14), after Flutter's: the recognizers interested in a pointer sequence (a press and what follows until the release) compete in that pointer's arena, and one wins. |
|
gfx
Package gfx holds Tela's basic graphics types and the DisplayList, the contract between the paint phase, the CPU rasterizer and any future GPU backend.
|
Package gfx holds Tela's basic graphics types and the DisplayList, the contract between the paint phase, the CPU rasterizer and any future GPU backend. |
|
gfx/raster
Package raster is Tela's CPU rasterizer: it renders a gfx.DisplayList into an *image.RGBA.
|
Package raster is Tela's CPU rasterizer: it renders a gfx.DisplayList into an *image.RGBA. |
|
icongen
Package icongen turns a directory of SVG files into a Go file with one tela.Icon variable per file; it is the engine of "tela icons gen" (ADR-102).
|
Package icongen turns a directory of SVG files into a Go file with one tela.Icon variable per file; it is the engine of "tela icons gen" (ADR-102). |
|
layout
Package layout is Tela's layout engine (ARCHITECTURE section 9): block layout without margin collapsing, flexbox, CSS grid, relative, absolute and fixed positioning, intrinsic sizes, leaf measurement by callback, caching and relayout boundaries.
|
Package layout is Tela's layout engine (ARCHITECTURE section 9): block layout without margin collapsing, flexbox, CSS grid, relative, absolute and fixed positioning, intrinsic sizes, leaf measurement by callback, caching and relayout boundaries. |
|
lint
Package lint is the engine of `tela lint` (ADR-173): it checks Go source statically for the mistakes that dev mode reports at run time, so that they fail in CI with dev mode off.
|
Package lint is the engine of `tela lint` (ADR-173): it checks Go source statically for the mistakes that dev mode reports at run time, so that they fail in CI with dev mode off. |
|
platform
Package platform is the lowest layer of Tela: the contract between the framework and one OS windowing system (a backend).
|
Package platform is the lowest layer of Tela: the contract between the framework and one OS windowing system (a backend). |
|
platform/auto
Package auto selects the platform backend for the current environment.
|
Package auto selects the platform backend for the current environment. |
|
platform/headless
Package headless is an in-memory platform backend for tests, snapshots, telatest and the CLI.
|
Package headless is an in-memory platform backend for tests, snapshots, telatest and the CLI. |
|
platform/internal/evdev
Package evdev maps Linux evdev key codes (linux/input-event-codes.h) to physical keys (platform.KeyCode, W3C "code" values).
|
Package evdev maps Linux evdev key codes (linux/input-event-codes.h) to physical keys (platform.KeyCode, W3C "code" values). |
|
platform/internal/freedesktop
Package freedesktop implements the desktop services of the Linux backends (platform.Desktop, ADR-178, ADR-179) over the D-Bus session bus with github.com/godbus/dbus/v5.
|
Package freedesktop implements the desktop services of the Linux backends (platform.Desktop, ADR-178, ADR-179) over the D-Bus session bus with github.com/godbus/dbus/v5. |
|
platform/internal/keyboard
Package keyboard holds the OS-independent part of the platform keyboard contract (see package platform, "Keyboard"), shared by the backends: the set of keys a window has delivered presses for, which turns into synthetic releases when the window loses focus, and the reporting of keyboard setup errors.
|
Package keyboard holds the OS-independent part of the platform keyboard contract (see package platform, "Keyboard"), shared by the backends: the set of keys a window has delivered presses for, which turns into synthetic releases when the window loses focus, and the reporting of keyboard setup errors. |
|
platform/internal/loop
Package loop is the OS-independent half of a platform backend's event loop: the part of the platform contract (see package platform, "Loop contract" and "Frames and pacing") that does not depend on how the OS reports events.
|
Package loop is the OS-independent half of a platform backend's event loop: the part of the platform contract (see package platform, "Loop contract" and "Frames and pacing") that does not depend on how the OS reports events. |
|
platform/internal/xkb
Package xkb is a minimal pure-Go implementation of the parts of XKB a Wayland client needs (no libxkbcommon, no cgo):
|
Package xkb is a minimal pure-Go implementation of the parts of XKB a Wayland client needs (no libxkbcommon, no cgo): |
|
platform/platformtest
Package platformtest is a backend-agnostic conformance suite for the platform contract.
|
Package platformtest is a backend-agnostic conformance suite for the platform contract. |
|
platform/remote
Package remote is a platform backend that runs over IPC: an app (the child) uses a Backend whose windows live in another process (the host), which shows them on the windows of a real backend through a Host.
|
Package remote is a platform backend that runs over IPC: an app (the child) uses a Backend whose windows live in another process (the host), which shows them on the windows of a real backend through a Host. |
|
platform/wayland
Package wayland is Tela's Wayland platform backend: a pure-Go client of the compositor that implements the platform contract (see package platform) over the wire runtime and the generated protocol bindings.
|
Package wayland is Tela's Wayland platform backend: a pure-Go client of the compositor that implements the platform contract (see package platform) over the wire runtime and the generated protocol bindings. |
|
platform/wayland/protocols
Package protocols holds the Go bindings of the Wayland protocols Tela uses, generated from the XML files in xml/ by internal/platform/wayland/wlgen.
|
Package protocols holds the Go bindings of the Wayland protocols Tela uses, generated from the XML files in xml/ by internal/platform/wayland/wlgen. |
|
platform/wayland/wire
Package wire is the client side of the Wayland wire protocol: connection setup, the object ID map, message encoding and decoding, file-descriptor passing and buffered, non-blocking I/O.
|
Package wire is the client side of the Wayland wire protocol: connection setup, the object ID map, message encoding and decoding, file-descriptor passing and buffered, non-blocking I/O. |
|
platform/wayland/wlgen
command
Command wlgen generates Go bindings from Wayland protocol XML files.
|
Command wlgen generates Go bindings from Wayland protocol XML files. |
|
platform/win32
Package win32 is the Windows platform backend.
|
Package win32 is the Windows platform backend. |
|
platform/x11
Package x11 is the X11 platform backend, the Linux fallback when no Wayland compositor is available.
|
Package x11 is the X11 platform backend, the Linux fallback when no Wayland compositor is available. |
|
platform/x11/internal/xgb
Package XGB provides the X Go Binding, which is a low-level API to communicate with the core X protocol and many of the X extensions.
|
Package XGB provides the X Go Binding, which is a low-level API to communicate with the core X protocol and many of the X extensions. |
|
platform/x11/internal/xgb/bigreq
Package bigreq is the X client API for the BIG-REQUESTS extension.
|
Package bigreq is the X client API for the BIG-REQUESTS extension. |
|
platform/x11/internal/xgb/xproto
Package xproto is the X client API for the extension.
|
Package xproto is the X client API for the extension. |
|
semantics
Package semantics holds Tela's semantics tree (ARCHITECTURE sections 4, 8 and 16): a parallel tree of roles, names, states and actions derived from the Element and Box trees, used by accessibility backends, tests (telatest's ByRole and ByText) and agents (the aria snapshot of `tela inspect --aria` and `tela mcp`).
|
Package semantics holds Tela's semantics tree (ARCHITECTURE sections 4, 8 and 16): a parallel tree of roles, names, states and actions derived from the Element and Box trees, used by accessibility backends, tests (telatest's ByRole and ByText) and agents (the aria snapshot of `tela inspect --aria` and `tela mcp`). |
|
style
Package style is Tela's style layer (ARCHITECTURE section 10): the theme and design tokens, the Tailwind v4 class parser, and the resolution of classes and typed props into a ComputedStyle.
|
Package style is Tela's style layer (ARCHITECTURE section 10): the theme and design tokens, the Tailwind v4 class parser, and the resolution of classes and typed props into a ComputedStyle. |
|
style/gen
command
Command gen generates internal/style/theme_gen.go, the default theme, from the Tailwind v4 theme source vendored in tailwind/theme.css (ADR-018, ADR-037).
|
Command gen generates internal/style/theme_gen.go, the default theme, from the Tailwind v4 theme source vendored in tailwind/theme.css (ADR-018, ADR-037). |
|
svg
Package svg parses static SVG documents and draws them as gfx display list commands, for icons and SVG images (ADR-095).
|
Package svg parses static SVG documents and draws them as gfx display list commands, for icons and SVG images (ADR-095). |
|
testutil/chromium
Package chromium is the shared part of the harnesses that regenerate Chromium references (TELA_CHROMIUM=1): compiling a page's Tailwind CSS with the Tailwind CLI, taking a screenshot and dumping a result computed by the page's script.
|
Package chromium is the shared part of the harnesses that regenerate Chromium references (TELA_CHROMIUM=1): compiling a page's Tailwind CSS with the Tailwind CLI, taking a screenshot and dumping a result computed by the page's script. |
|
testutil/damagetest
Package damagetest is the scroll-and-hover damage check shared by the tests of the examples.
|
Package damagetest is the scroll-and-hover damage check shared by the tests of the examples. |
|
testutil/golden
Package golden compares images produced by tests against golden PNG files.
|
Package golden compares images produced by tests against golden PNG files. |
|
testutil/rawwin
Package rawwin is the headless window that the tests of aria and kit drive with platform events when telatest has no action for what they test yet (touch and pen pointers, a pen hover, key repeats, the semantics click of an agent) or when they read the tree's internals; a click with the secondary button, a tap and a long press have telatest actions (ClickAt with Right, Tap, LongPress).
|
Package rawwin is the headless window that the tests of aria and kit drive with platform events when telatest has no action for what they test yet (touch and pen pointers, a pen hover, key repeats, the semantics click of an agent) or when they read the tree's internals; a click with the secondary button, a tap and a long press have telatest actions (ClickAt with Right, Tap, LongPress). |
|
testutil/wine
Package wine tells tests whether a Windows test binary runs under Wine instead of on real Windows, so the tests that check what Wine does not implement can skip there (and only there).
|
Package wine tells tests whether a Windows test binary runs under Wine instead of on real Windows, so the tests that check what Wine does not implement can skip there (and only there). |
|
text
Package text is Tela's text layer (ARCHITECTURE section 11): the font layer (typefaces, face instances, metrics, font matching and fallback, color glyphs) and, on top of it, paragraph layout (shaping, bidi, line breaking, alignment, truncation, painting); see "Paragraph layout".
|
Package text is Tela's text layer (ARCHITECTURE section 11): the font layer (typefaces, face instances, metrics, font matching and fallback, color glyphs) and, on top of it, paragraph layout (shaping, bidi, line breaking, alignment, truncation, painting); see "Paragraph layout". |
|
text/edit
Package edit is the editable plain-text model under core's Input and Textarea (ARCHITECTURE section 11, ADR-065): a UTF-8 value, a selection, an IME composition, an undo history and the commands that key and pointer handling produce.
|
Package edit is the editable plain-text model under core's Input and Textarea (ARCHITECTURE section 11, ADR-065): a UTF-8 value, a selection, an IME composition, an undo history and the commands that key and pointer handling produce. |
|
utf16x
Package utf16x measures text and converts text offsets between UTF-16 code units and UTF-8 bytes.
|
Package utf16x measures text and converts text offsets between UTF-16 code units and UTF-8 bytes. |
|
Package kit provides styled components for Tela apps: a button, a card, a text field, in the look of shadcn/ui (style "new-york-v4", at the registry commit ShadcnCommit), built on the behavior hooks of package aria and the classes of package h.
|
Package kit provides styled components for Tela apps: a button, a card, a text field, in the look of shadcn/ui (style "new-york-v4", at the registry commit ShadcnCommit), built on the behavior hooks of package aria and the classes of package h. |
|
Package telatest tests Tela UIs without a window: it mounts a tree in a headless window, simulates a user with real pointer and key events, finds elements by role and text like Playwright, and compares the pixels with goldens (ARCHITECTURE section 17).
|
Package telatest tests Tela UIs without a window: it mounts a tree in a headless window, simulates a user with real pointer and key events, finds elements by role and text like Playwright, and compares the pixels with goldens (ARCHITECTURE section 17). |








