Repository navigation
API Reference h
import "github.com/kuronosu/tela/h"This page is generated from the doc comments of the package; the same text is on pkg.go.dev. For guides and examples, start at the wiki home.
- Overview
-
Functions:
A·Alt·AriaChecked·AriaDescription·AriaDisabled·AriaExpanded·AriaHidden·AriaLabel·AriaLabelledBy·AriaLevel·AriaPressed·AriaSelected·AriaValueMax·AriaValueMin·AriaValueNow·AriaValueText·AutoComplete·AutoFocus·Bind·BindValue·Button·Class·Code·DefaultValue·Disabled·Div·Em·For·Form·Fragment·H1·H2·H3·H4·H5·H6·Href·Id·If·IfElse·Img·Input·Key·Label·Map·MapIndex·MaxLength·Name·OnBeforeInput·OnBlur·OnChange·OnClick·OnCompositionEnd·OnCompositionStart·OnCompositionUpdate·OnContextMenu·OnDoubleClick·OnDrag·OnDragEnd·OnDragStart·OnFocus·OnKeyDown·OnKeyUp·OnLongPress·OnMouseDown·OnMouseEnter·OnMouseLeave·OnMouseMove·OnMouseUp·OnPointerCancel·OnPointerDown·OnPointerEnter·OnPointerLeave·OnPointerMove·OnPointerUp·OnScroll·OnSelect·OnSubmit·OnWheel·Overscan·P·Placeholder·ReadOnly·Ref·Role·Rows·Span·SpellCheck·Src·Strong·Style·TabIndex·Text·TextFn·Textarea·Textf·Type·Value·VirtualList
Package h holds the elements, props and helpers of a Tela UI. It is designed to be dot-imported, so that markup reads like JSX:
import (
"github.com/kuronosu/tela"
. "github.com/kuronosu/tela/h"
)
func Greeting(name string) tela.Node {
return Div(Class("flex items-center gap-3 rounded-xl bg-white p-4 shadow-md"),
H2(Class("text-lg font-semibold text-slate-900"), Textf("Hello, %s", name)),
If(name == "", P(Class("text-slate-500"), Text("Who are you?"))),
)
}To keep collisions with the importing package unlikely, h exports only element constructors (Div, Span, Text, H1 to H6, P, Button, Img, Strong, Em, Code, A, and the form elements Input, Textarea, Form and Label), props (Class, Style, Key, Disabled, Src, Alt, Href, Id, the focus props TabIndex, AutoFocus and Ref, the semantics props Role and Aria..., the field props Value, DefaultValue, Bind, BindValue, Type, Rows, Placeholder, ReadOnly, MaxLength, AutoComplete, SpellCheck, Name and For, and the event handlers OnClick, OnDoubleClick, OnContextMenu, OnMouse..., OnPointer..., OnDrag..., OnLongPress, OnKey..., OnFocus, OnBlur, OnWheel, OnScroll, OnChange, OnBeforeInput, OnComposition..., OnSelect and OnSubmit), the helpers If, IfElse (for nodes and props alike), Map, MapIndex, Fragment and TextFn (text that a Value changes without rebuilding the component), and VirtualList with its Overscan prop.
Everything else (App, Run, hooks, events, colors, typed style props) lives in package tela.
An element takes its props and children in one list, in any order:
Div(Class("p-4"), Key(id), Text("hi"), Span(Text("there")))Props apply in order, so a later Class wins over an earlier one and Style wins over every class. Children are nodes only; wrap text in Text or Textf. The parameters are tela.Child, so passing a bare string is a compile error.
Elements have the defaults of Tailwind's Preflight: no margins, padding or borders, headings in the surrounding font size, block images. A Button has no background of its own, and neither has an Input or a Textarea: style fields with classes.
Text fields are controlled, like React's:
func Signup() tela.Node {
email, setEmail := tela.UseState("")
return Form(Class("flex flex-col gap-2"), OnSubmit(func(tela.SubmitEvent) { register(email) }),
Label(For("email"), Class("text-sm font-medium"), Text("Email")),
Input(Id("email"), Type("email"), Bind(email, setEmail), Placeholder("you@example.com"),
Class("rounded-md border border-slate-300 px-3 py-2 invalid:border-red-500")),
Button(Class("rounded-md bg-indigo-600 px-4 py-2 text-white"), Text("Sign up")))
}func A(children ...tela.Child) tela.NodeA returns a link: inline text with a destination given by Href. Links are unstyled (no color or underline), like Tailwind's Preflight; style them with classes.
func Alt(text string) tela.PropAlt sets the alternative text of an Img: its accessible name. Use Alt("") for a decorative image, which the semantics tree leaves out. It panics on other elements.
Img(Src(avatar), Alt("Ada Lovelace"), Class("size-10 rounded-full"))func AriaChecked(checked bool) tela.PropAriaChecked sets the checked state of a checkbox, radio or switch.
Div(Role("checkbox"), AriaChecked(done), TabIndex(0), Text("Done"))func AriaDescription(description string) tela.PropAriaDescription sets the accessible description of an element: a longer hint read after its name.
Button(AriaDescription("Removes the file for everyone"), Text("Delete"))func AriaDisabled(disabled bool) tela.PropAriaDisabled marks an element and its descendants as disabled for assistive technologies without making them inert (Disabled does both).
Button(AriaDisabled(true), OnClick(explainWhy), Text("Publish"))func AriaExpanded(expanded bool) tela.PropAriaExpanded sets whether what a button or link controls (a menu, a disclosure) is expanded.
Button(AriaExpanded(open), OnClick(toggle), Text("Options"))func AriaHidden(hidden bool) tela.PropAriaHidden hides an element and its subtree from the semantics tree when true (decorations, duplicated content), without hiding it on screen.
Span(AriaHidden(true), Text("★"))func AriaLabel(label string) tela.PropAriaLabel sets the accessible name of an element, for elements whose content does not name them (an icon button).
Button(AriaLabel("Close"), OnClick(close), closeIcon)func AriaLabelledBy(ref tela.ElementRef) tela.PropAriaLabelledBy names an element with the text of another one, attached to ref with Ref.
title := tela.UseElementRef()
Div(Role("dialog"), AriaLabelledBy(title),
H2(Ref(title), Text("Delete file?")), ...)func AriaLevel(level int) tela.PropAriaLevel sets the level of an element with Role("heading") (H1 to H6 have theirs), or of a list item, row or tree item.
Div(Role("heading"), AriaLevel(2), Text("Billing"))func AriaPressed(pressed bool) tela.PropAriaPressed makes a button a toggle button and sets whether it is pressed.
Button(AriaPressed(bold), OnClick(toggleBold), Text("B"))func AriaSelected(selected bool) tela.PropAriaSelected sets whether a tab, option, row or cell is selected.
Button(Role("tab"), AriaSelected(active == "general"), Text("General"))func AriaValueMax(value float64) tela.PropAriaValueMax sets the maximum value of a range widget.
func AriaValueMin(value float64) tela.PropAriaValueMin sets the minimum value of a range widget.
func AriaValueNow(value float64) tela.PropAriaValueNow sets the current value of a range widget (Role("slider"), "progressbar", "spinbutton", "meter").
Div(Role("progressbar"), AriaValueNow(40), AriaValueMin(0), AriaValueMax(100), bar)func AriaValueText(text string) tela.PropAriaValueText sets the value of a range widget as people read it.
Div(Role("slider"), AriaValueNow(3), AriaValueText("Medium"), track)func AutoComplete(s string) tela.PropAutoComplete is HTML's autocomplete hint ("email", "current-password"...). It is accepted for familiarity; Tela has no autofill.
func AutoFocus(on bool) tela.PropAutoFocus focuses the element when it first appears, in the frame that shows it (React's autoFocus), unless another element took the focus since it was mounted. When several AutoFocus elements appear at once, the first in tree order wins.
Button(AutoFocus(true), OnClick(confirm), Text("OK"))func Bind(text string, set tela.Setter[string]) tela.PropBind makes a text field controlled by a state: Value(text) plus an OnChange that calls set.Set(e.Value).
name, setName := tela.UseState("")
Input(Bind(name, setName))func BindValue(v *tela.Value[string]) tela.PropBindValue binds a text field to v, from tela.UseValue: the field shows v's value and writes every edit to it without rebuilding the component (only what reads v rebuilds or repaints), and follows v.Set from anywhere.
query := tela.UseValue("")
Input(Type("search"), BindValue(query))func Button(children ...tela.Child) tela.NodeButton returns a button. It has no background, border or padding of its own (Tailwind's Preflight): style it with classes. Its width fits its content, it sits on the line of the text around it (inline-block, on the baseline; Class("align-middle") moves it), and it shows the pointer cursor. Clicks are handled with OnClick.
func Class(classes string) tela.PropClass adds Tailwind v4 classes ("flex items-center gap-4 hover:bg-white/10"). It may repeat on an element; the strings concatenate in order and a later class wins over an earlier one that sets the same property. An unknown class is reported in dev mode (TELA_DEV=1) with the closest valid one.
Example
// A class is a Tailwind v4 class: bg-red-500 paints the box red.
swatch := Div(Class("size-16 bg-red-500"))
img, _ := telatest.Render(swatch, telatest.Size(64, 64))
fmt.Println(img.RGBAAt(32, 32))
// Output:
// {251 44 54 255}func Code(children ...tela.Child) tela.NodeCode returns inline text in the monospace font.
func DefaultValue(s string) tela.PropDefaultValue gives an uncontrolled text field its initial value; later values are ignored (React's defaultValue). Read the value with tela.ElementRef's Value.
func Disabled(disabled bool) tela.PropDisabled disables an element, like a disabled form control: no pointer handler runs on it or on its descendants, and their events do not reach it (its ancestors still receive them); it does not become active, and neither it nor its descendants can take the focus (a focused one loses it). Its disabled: and enabled: variants apply, as do the group-disabled: and peer-disabled: variants of others.
Button(Disabled(saving), Class("bg-indigo-500 disabled:opacity-50 disabled:cursor-not-allowed"),
OnClick(save), Text("Save"))Example
save := Button(Disabled(true), Class("disabled:opacity-50"), Text("Save"))
yaml, _ := telatest.AriaSnapshot(save)
fmt.Println(yaml)
// Output:
// - button "Save" [disabled]func Div(children ...tela.Child) tela.NodeDiv returns a block container, the general-purpose box. Its children stack vertically unless its classes say otherwise (flex).
Example
card := Div(Class("flex flex-col gap-2 rounded-xl bg-white p-6 shadow-md"),
H2(Class("text-lg font-semibold"), Text("Inbox")),
P(Class("text-slate-500"), Textf("%d unread messages", 3)),
Button(Class("rounded-md bg-indigo-600 px-4 py-2 text-white"), Text("Open")),
)
yaml, _ := telatest.AriaSnapshot(card)
fmt.Println(yaml)
// Output:
// - heading "Inbox" [level=2]
// - paragraph: 3 unread messages
// - button "Open"func Em(children ...tela.Child) tela.NodeEm returns italic inline text.
func For(id string) tela.PropFor makes a Label label the element whose Id is id.
func Form(children ...tela.Child) tela.NodeForm returns a form: a block that groups fields and buttons. Enter in one of its single-line fields, or a click on a Button inside it, submits it (OnSubmit); a Button with Type("reset") restores the DefaultValue of its uncontrolled fields. Buttons inside a form are submit buttons unless Type says "button".
Form(OnSubmit(func(tela.SubmitEvent) { save() }),
Input(Bind(name, setName)),
Button(Type("submit"), Text("Save")))func Fragment(children ...tela.Child) tela.NodeFragment groups children without a box of their own: they become children of the fragment's parent. It takes only Key as a prop.
func H1(children ...tela.Child) tela.NodeH1 returns a level 1 heading. Headings are unstyled: size and weight come from classes (text-5xl font-bold).
func H2(children ...tela.Child) tela.NodeH2 returns a level 2 heading. See H1.
func H3(children ...tela.Child) tela.NodeH3 returns a level 3 heading. See H1.
func H4(children ...tela.Child) tela.NodeH4 returns a level 4 heading. See H1.
func H5(children ...tela.Child) tela.NodeH5 returns a level 5 heading. See H1.
func H6(children ...tela.Child) tela.NodeH6 returns a level 6 heading. See H1.
func Href(url string) tela.PropHref sets the destination of an A. Tela does not open links by itself yet: handle OnClick.
func Id(id string) tela.PropId gives an element an identifier, unique in the window, that a Label's For refers to.
func If[C tela.Child](cond bool, c C) CIf returns c when cond holds and the zero value of its type otherwise. It takes a node or a prop, so it works for children and for props: the zero Node renders nothing but keeps the positions of the siblings after it (toggling cond does not remount them), and the zero Prop changes nothing.
Div(
If(loading, Spinner()),
If(selected, Class("ring-2 ring-indigo-500")),
If(err != nil, P(Class("text-red-600"), Text(err.Error()))),
)Every argument of a call is evaluated: If(user != nil, Text(user.Name)) dereferences a nil user. Use an ordinary if for that case.
Example
screen := func(loading bool, err error) tela.Node {
return Div(
If(loading, P(Text("Loading..."))),
If(err != nil, P(Class("text-red-600"), Text("Something failed"))),
P(Text("Done")),
)
}
yaml, _ := telatest.AriaSnapshot(screen(true, nil))
fmt.Println(yaml)
// Output:
// - paragraph: Loading...
// - paragraph: Donefunc IfElse[C tela.Child](cond bool, a, b C) CIfElse returns a when cond holds and b otherwise: two nodes, or two props.
Div(IfElse(open, Class("h-64"), Class("h-0")),
IfElse(loading, Spinner(), Text(label)))Both arguments are evaluated, like those of If.
Example
toggle := func(on bool) tela.Node {
return Button(
IfElse(on, Class("bg-indigo-600 text-white"), Class("bg-slate-200 text-slate-900")),
AriaPressed(on),
IfElse(on, Text("On"), Text("Off")),
)
}
yaml, _ := telatest.AriaSnapshot(toggle(true))
fmt.Println(yaml)
// Output:
// - button "On" [pressed]func Img(children ...tela.Child) tela.NodeImg returns an image. Give it a source with Src; without Class or Style sizes it is drawn at the size of the image, shrunk to fit its container.
func Input(children ...tela.Child) tela.NodeInput returns a single-line text field. Like a browser's, it edits its value with the keyboard (typing, arrows and Ctrl+arrows, Home/End, Backspace/Delete, Ctrl+A, Ctrl+Z/Ctrl+Y, the clipboard), the mouse (click, drag, double and triple click) and input methods (IME), and submits its Form on Enter. Give it a value with Value and OnChange (or Bind: controlled), DefaultValue (uncontrolled) or BindValue, a Type, a Placeholder and a Label.
It has Tailwind's Preflight defaults: no border, padding or background (style it with classes), the inherited font, and a width of about 20 characters (Chromium's size=20) unless a class sets one; its text is centered vertically in its height. field-sizing-content sizes it to its text. It takes no children.
Input(Type("email"), Placeholder("you@example.com"), Bind(email, setEmail),
Class("w-full rounded-md border border-slate-300 px-3 py-2 placeholder:text-slate-400 "+
"focus:outline-2 focus:outline-indigo-500"))Example
// A controlled field: the value comes from state, and Bind writes
// every edit back to it.
signup := func() tela.Node {
email, setEmail := tela.UseState("ana@example.com")
return Form(Class("flex flex-col gap-2"),
Label(For("email"), Text("Email")),
Input(Id("email"), Type("email"), Bind(email, setEmail),
Class("rounded-md border border-slate-300 px-3 py-2")),
Button(Class("rounded-md bg-indigo-600 px-4 py-2 text-white"), Text("Sign up")),
)
}
yaml, _ := telatest.AriaSnapshot(tela.C(signup))
fmt.Println(yaml)
// Output:
// - text: Email
// - textbox "Email": ana@example.com
// - button "Sign up"func Key(k any) tela.PropKey gives an element an identity among its siblings: when a list changes, the element with the same key keeps its state wherever it moves. k must be a non-nil comparable value, such as an item ID; Key panics otherwise. Elements built from data (Map) should have keys.
func Label(children ...tela.Child) tela.NodeLabel returns a label for a field: inline text that gives the field its accessible name and focuses it when clicked. It labels the element whose Id is its For, or else the Input, Textarea or Button inside it.
Label(For("name"), Text("Name")), Input(Id("name"))
Label(Text("Name "), Input())func Map[T any](items []T, f func(T) tela.Node) tela.NodeMap returns the nodes of f applied to every item, as one list. Give each node a Key (the item's ID, not its index) so that reordering, inserting and removing keeps their state; in dev mode (TELA_DEV=1) a list of two or more nodes without keys is reported.
Map(todos, func(t Todo) tela.Node {
return Div(Key(t.ID), Text(t.Title))
})Example
type Todo struct {
ID int
Text string
}
todos := []Todo{{1, "Write docs"}, {2, "Ship it"}}
list := Div(Role("list"),
Map(todos, func(t Todo) tela.Node {
return Div(Key(t.ID), Role("listitem"), Text(t.Text))
}),
)
yaml, _ := telatest.AriaSnapshot(list)
fmt.Println(yaml)
// Output:
// - list:
// - listitem: Write docs
// - listitem: Ship itfunc MapIndex[T any](items []T, f func(i int, item T) tela.Node) tela.NodeMapIndex is Map with the item's index as the first argument of f: for numbering, alternating styles and the first or last item. The same rules apply to keys: use an ID, not the index, when the list changes.
MapIndex(steps, func(i int, s Step) tela.Node {
return Div(Key(s.ID), Textf("%d. %s", i+1, s.Title))
})Example
steps := []string{"Mount", "Click", "Expect"}
list := Div(Role("list"),
MapIndex(steps, func(i int, s string) tela.Node {
return Div(Key(s), Role("listitem"), Textf("%d. %s", i+1, s))
}),
)
yaml, _ := telatest.AriaSnapshot(list)
fmt.Println(yaml)
// Output:
// - list:
// - listitem: 1. Mount
// - listitem: 2. Click
// - listitem: 3. Expectfunc MaxLength(n int) tela.PropMaxLength limits what the user can type or paste into a text field to n UTF-16 code units (HTML's maxlength: "😀" counts 2); a value set by the app may be longer. 0 means no limit.
func Name(s string) tela.PropName names a text field or a button in its form (HTML's name).
func OnBeforeInput(handler func(tela.InputEvent)) tela.PropOnBeforeInput calls handler before a text field's value changes by an edit of the user (the DOM's beforeinput); e.PreventDefault() cancels the edit (except an IME composition's). It bubbles.
func OnBlur(handler func(tela.FocusEvent)) tela.PropOnBlur calls handler when the element or one of its descendants loses the focus (React's onBlur: it bubbles), also when the window loses the keyboard focus. Unmounting the focused element does not call it.
Div(TabIndex(0), OnBlur(func(tela.FocusEvent) { commit() }))func OnChange(handler func(tela.ChangeEvent)) tela.PropOnChange calls handler after every edit of a text field's value (React's onChange: each keystroke, paste, cut, undo, and the text an IME composes, with e.IsComposing). It bubbles: on a Form it sees every field.
Input(Value(name), OnChange(func(e tela.ChangeEvent) { setName.Set(e.Value) }))func OnClick(handler func(tela.MouseEvent)) tela.PropOnClick calls handler when the element is clicked: the primary button is pressed and released over it or its descendants (the click goes to the nearest element that contains both the press and the release, as in the DOM), even if the pointer moved in between, unless the press became a drag or a long press. The event bubbles to the ancestors.
Button(OnClick(func(e tela.MouseEvent) { setCount.Set(count + 1) }), Text("+1"))Every handler prop panics when handler is nil.
func OnCompositionEnd(handler func(tela.CompositionEvent)) tela.PropOnCompositionEnd calls handler when an input method ends a composition, committed (e.Data) or canceled (""). It bubbles.
func OnCompositionStart(handler func(tela.CompositionEvent)) tela.PropOnCompositionStart calls handler when an input method (IME) starts composing text in a text field. It bubbles.
func OnCompositionUpdate(handler func(tela.CompositionEvent)) tela.PropOnCompositionUpdate calls handler when the text an input method composes changes (e.Data). It bubbles.
func OnContextMenu(handler func(tela.MouseEvent)) tela.PropOnContextMenu calls handler when the secondary (right) button is released over the element.
Div(OnContextMenu(func(e tela.MouseEvent) { openMenu(e.X, e.Y) }))func OnDoubleClick(handler func(tela.MouseEvent)) tela.PropOnDoubleClick calls handler on the second click of a double click: two clicks within 500 ms and 5 px (300 ms and 100 px for touch). The first click is not delayed: OnClick runs for both, with e.ClickCount 1 and 2.
Div(OnDoubleClick(func(tela.MouseEvent) { setEditing.Set(true) }), Text(title))func OnDrag(handler func(tela.DragEvent)) tela.PropOnDrag calls handler for every move of a drag (see OnDragStart), with the movement since the previous one (DeltaX, DeltaY) and since the press (OffsetX, OffsetY). Moves are batched: state set here is applied once per frame.
Div(OnDrag(func(e tela.DragEvent) { pos.Update(func(p float64) float64 { return p + float64(e.DeltaX) }) }))func OnDragEnd(handler func(tela.DragEvent)) tela.PropOnDragEnd calls handler when a drag ends: the button is released (or the press cancelled). VelocityX and VelocityY give the release velocity in pixels per second, for flings.
Div(OnDragEnd(func(e tela.DragEvent) {
if e.VelocityX > 800 {
dismiss()
}
}))func OnDragStart(handler func(tela.DragEvent)) tela.PropOnDragStart calls handler when a press on the element becomes a drag: the pointer moved more than a few pixels (4 for a mouse, 18 for touch and pens) before a long press. The press then does not click. Drag events go to the innermost element with drag handlers and do not bubble.
pos := tela.UseValue(0.0)
start := tela.UseRef(0.0)
Div(Class("size-10 rounded-full bg-indigo-500"),
Style(tela.TranslateFn(func() (x, y float32) { return float32(pos.Get()), 0 })),
OnDragStart(func(tela.DragEvent) { start.Current = pos.Get() }),
OnDrag(func(e tela.DragEvent) { pos.Set(start.Current + float64(e.OffsetX)) }),
)func OnFocus(handler func(tela.FocusEvent)) tela.PropOnFocus calls handler when the element or one of its descendants gains the focus (React's onFocus: it bubbles), and again when the window regains the keyboard focus.
Div(TabIndex(0), OnFocus(func(tela.FocusEvent) { setActive.Set(true) }))func OnKeyDown(handler func(tela.KeyboardEvent)) tela.PropOnKeyDown calls handler when a key is pressed (and again for each repeat while it is held, with e.Repeat) while the element or one of its descendants has the focus; the event bubbles. With nothing focused, key events go to the outermost element of the window's content. e.PreventDefault() cancels the key's default action: the focus move of Tab, the click of Enter or Space on a focused Button.
Div(OnKeyDown(func(e tela.KeyboardEvent) {
if e.Key == "Escape" {
close()
}
if e.Ctrl && e.Code == tela.CodeKeyS {
save()
}
}))func OnKeyUp(handler func(tela.KeyboardEvent)) tela.PropOnKeyUp calls handler when a key is released while the element or one of its descendants has the focus; the event bubbles. Keys held when the window loses the focus are released then. e.PreventDefault() cancels the click of Space on a focused Button.
Div(OnKeyUp(func(e tela.KeyboardEvent) {
if e.Key == tela.KeyShift {
setMulti.Set(false)
}
}))func OnLongPress(handler func(tela.PointerEvent)) tela.PropOnLongPress calls handler when the primary button (or a touch) stays pressed on the element for 500 ms without moving more than a few pixels (4 for a mouse, 18 for touch and pens). The press then does not click or drag. It goes to the innermost element with OnLongPress and does not bubble.
Div(OnLongPress(func(e tela.PointerEvent) { openMenu(e.X, e.Y) }), Text(name))func OnMouseDown(handler func(tela.MouseEvent)) tela.PropOnMouseDown calls handler when a button is pressed over the element.
Div(OnMouseDown(func(e tela.MouseEvent) {
if e.Button == tela.MouseButtonMiddle {
closeTab()
}
}))func OnMouseEnter(handler func(tela.MouseEvent)) tela.PropOnMouseEnter calls handler when the pointer enters the element or one of its descendants from outside. It does not bubble.
Div(OnMouseEnter(func(tela.MouseEvent) { setShowTip.Set(true) }))func OnMouseLeave(handler func(tela.MouseEvent)) tela.PropOnMouseLeave calls handler when the pointer leaves the element and its descendants. It does not bubble.
Div(OnMouseLeave(func(tela.MouseEvent) { setShowTip.Set(false) }))func OnMouseMove(handler func(tela.MouseEvent)) tela.PropOnMouseMove calls handler when the pointer moves over the element. Moves are batched: state set here is applied once per frame.
Div(OnMouseMove(func(e tela.MouseEvent) { pos.Set(e.LocalX) }))func OnMouseUp(handler func(tela.MouseEvent)) tela.PropOnMouseUp calls handler when a button is released over the element.
Div(OnMouseUp(func(tela.MouseEvent) { setPressed.Set(false) }))func OnPointerCancel(handler func(tela.PointerEvent)) tela.PropOnPointerCancel calls handler when the system takes a pointer away during a press (a touch turned into a system gesture, a lost grab): treat it as an aborted press, not a release.
Div(OnPointerCancel(func(tela.PointerEvent) { discardStroke() }))func OnPointerDown(handler func(tela.PointerEvent)) tela.PropOnPointerDown calls handler when a pointer (mouse, touch or pen) is pressed over the element: the first button of a mouse, the contact of a touch. While a touch or pen is down its events go to this element. e.PreventDefault() suppresses the mouse events of the press (not the click).
Div(OnPointerDown(func(e tela.PointerEvent) {
if e.Kind == tela.PointerPen {
startStroke(e.LocalX, e.LocalY)
}
}))func OnPointerEnter(handler func(tela.PointerEvent)) tela.PropOnPointerEnter calls handler when a pointer enters the element or one of its descendants. It does not bubble.
Div(OnPointerEnter(func(e tela.PointerEvent) { setHoverKind.Set(e.Kind) }))func OnPointerLeave(handler func(tela.PointerEvent)) tela.PropOnPointerLeave calls handler when a pointer leaves the element and its descendants. It does not bubble.
Div(OnPointerLeave(func(tela.PointerEvent) { setHovered.Set(false) }))func OnPointerMove(handler func(tela.PointerEvent)) tela.PropOnPointerMove calls handler when a pointer moves over the element (or, for a touch or pen that pressed it, anywhere), and when a button is pressed or released while another is held.
Div(OnPointerMove(func(e tela.PointerEvent) { addPoint(e.LocalX, e.LocalY, e.Pressure) }))func OnPointerUp(handler func(tela.PointerEvent)) tela.PropOnPointerUp calls handler when the last button of a pointer is released over the element.
Div(OnPointerUp(func(tela.PointerEvent) { endStroke() }))func OnScroll(handler func(tela.ScrollEvent)) tela.PropOnScroll calls handler after the element's scroll offsets changed, by the user or by code: at most once per frame, on the scrolling element itself (it does not bubble), with its offsets and sizes. To tie a style to the scroll position without rebuilding, use tela.UseScroll instead.
Div(Class("h-96 overflow-y-auto"), OnScroll(func(e tela.ScrollEvent) {
setAtTop.Set(e.ScrollTop == 0)
}), rows)func OnSelect(handler func(tela.SelectEvent)) tela.PropOnSelect calls handler when the user selects text in a text field, or code changes its selection (e.Start, e.End). It bubbles.
func OnSubmit(handler func(tela.SubmitEvent)) tela.PropOnSubmit calls handler when a Form is submitted: Enter in one of its single-line fields, or a click on a submit Button inside it. It bubbles.
func OnWheel(handler func(tela.WheelEvent)) tela.PropOnWheel calls handler when a mouse wheel turns or a touchpad scrolls over the element (the DOM's wheel event). The event bubbles. Unless a handler calls e.PreventDefault(), the nearest scrolling element (with overflow-auto or overflow-scroll) that can move in that direction scrolls, chaining to its scrolling ancestors at its edges unless it has overscroll-contain.
Div(OnWheel(func(e tela.WheelEvent) {
if e.Ctrl {
e.PreventDefault()
zoomBy(-e.DeltaY)
}
}))func Overscan(n int) tela.PropOverscan sets how many items a VirtualList builds beyond each side of the visible ones: 5 by default. Tela builds a new window before painting the frame, so the overscan is not needed to hide blank areas while scrolling fast; it makes most scroll frames paint-only (the window moves only when the visible items reach its edge) and keeps the next items ready for Tab and the arrow keys. More items cost more layout and paint per frame; 0 rebuilds the list on every scroll that shows a new item.
VirtualList(len(rows), estimate, render, Overscan(10))func P(children ...tela.Child) tela.NodeP returns a paragraph of text and inline elements (Span, Strong, Em, Code, A).
func Placeholder(s string) tela.PropPlaceholder sets the text a text field shows while it is empty, in the field's color at half opacity unless placeholder: classes style it.
func ReadOnly(on bool) tela.PropReadOnly makes a text field read-only: it can be focused, selected and copied, but not edited; it matches read-only:.
func Ref(ref tela.ElementRef) tela.PropRef attaches ref, from tela.UseElementRef, to the element: while it is mounted, ref.Focus(), ref.Blur(), ref.Focused() and the scroll methods (ref.ScrollTo, ScrollBy, ScrollIntoView, ScrollOffset) act on it, and tela.UseScroll(ref) follows its scroll offsets. It panics on the zero ElementRef.
input := tela.UseElementRef()
tela.UseEffect(func() func() {
input.Focus()
return nil
}, nil)
return Div(Ref(input), TabIndex(0), Class("focus:ring-2"), Text("Name"))func Role(role string) tela.PropRole sets the ARIA role of an element (WAI-ARIA 1.2), for components built from divs: Role("switch"), Role("dialog"), Role("tab"). An unknown role panics in dev mode (TELA_DEV=1) with the closest valid one.
Button(Role("switch"), AriaChecked(on), AriaLabel("Wi-Fi"),
OnClick(func(tela.MouseEvent) { setOn.Set(!on) }), knob)Example
enabled := true
toggle := Button(Role("switch"), AriaChecked(enabled), AriaLabel("Wi-Fi"),
Class("h-6 w-11 rounded-full bg-indigo-600"))
yaml, _ := telatest.AriaSnapshot(toggle)
fmt.Println(yaml)
// Output:
// - switch "Wi-Fi" [checked]func Rows(n int) tela.PropRows sets how many lines tall a Textarea is (2 by default).
func Span(children ...tela.Child) tela.NodeSpan returns an inline container. Inside a paragraph of text it styles a run of that text (color, weight...) and is a CSS inline box: its background, border, rounded corners and horizontal padding and margins apply on every line it is on (Class("rounded bg-slate-100 px-1")). With Class("inline-block") or "inline-flex" it is laid out as a box that sits on the line. As a child of a flex container it is a flex item.
func SpellCheck(on bool) tela.PropSpellCheck is HTML's spellcheck hint. It is accepted and ignored: Tela has no spell checker.
func Src(img image.Image) tela.PropSrc sets the image of an Img. Pass the same image.Image value on every build: it is converted for drawing only when another image is given.
func Strong(children ...tela.Child) tela.NodeStrong returns bold inline text.
func Style(props ...tela.StyleProp) tela.PropStyle adds typed style props from package tela, for values computed in code:
Style(tela.Width(w), tela.Bg(tela.Hex("#1e1e2e")))They apply after the element's classes and win over them.
Example
// Typed style props come from package tela and win over classes.
swatch := Div(Class("size-16 bg-red-500"), Style(tela.Bg(tela.RGB(0, 128, 255))))
img, _ := telatest.Render(swatch, telatest.Size(64, 64))
fmt.Println(img.RGBAAt(32, 32))
// Output:
// {0 128 255 255}func TabIndex(i int) tela.PropTabIndex makes an element focusable (a Button already is) and places it in the Tab order: 0 in tree order, -1 out of it (focusable by click and by ElementRef.Focus only). A positive index puts the element before every 0, in ascending order; avoid it, since the Tab order then no longer follows the layout. Disabled, display: none and invisible elements are not focusable; the focused element is blurred when it becomes one of them.
Div(TabIndex(0), Class("rounded-lg p-4 focus-visible:ring-2 focus-visible:ring-indigo-500"),
OnKeyDown(func(e tela.KeyboardEvent) {
if e.Key == tela.KeyEnter {
open()
}
}),
Text("Inbox"))func Text(s string) tela.NodeText returns a text node. The text is laid out in the paragraph of its nearest block ancestor and styled by its parent element.
func TextFn(fn func() string) tela.NodeTextFn returns a text node whose content is read by fn in a later phase, not when the component builds: a change of a tela.Value that fn read lays out only the paragraph that holds the text, and no component builds. It is Text for what changes every frame, such as a clock or a time label:
pos := tela.UseValue(0.0) // seconds, set from a ticker
Span(Class("tabular-nums"), TextFn(func() string { return clock(pos.Get()) }))It lays out like Text, inside paragraphs next to other text and inline elements. fn runs on the UI thread, when the paragraph is assembled (and the Values it read changed): it should only compute the string. A closure that captures the component's state is replaced by the new one whenever the component builds.
func Textarea(children ...tela.Child) tela.NodeTextarea returns a multi-line text field: like Input, but Enter inserts a line break, its text wraps, and it scrolls vertically (overflow auto) when the text is taller than it. It is Rows lines tall (2 by default) and about 20 characters wide unless classes size it; field-sizing-content makes it grow with its text (between min-h-* and max-h-*). It takes no children: give its text with Value, DefaultValue or BindValue.
Textarea(Rows(4), Placeholder("Write a message"), Bind(msg, setMsg),
Class("w-full rounded-md border border-slate-300 p-2"))func Textf(format string, args ...any) tela.NodeTextf returns a text node formatted like fmt.Sprintf.
func Type(t string) tela.PropType sets the type of an Input: "text" (the default), "password" (the value shows as dots and cannot be copied), "email", "search", "url", "tel" or "number" (a hint for input methods; the value stays text, with no spinner). "email" and "url" values that are not valid match invalid:. On a Button it sets "submit" (the default inside a Form), "button" or "reset". An unknown type panics in dev mode.
func Value(s string) tela.PropValue makes a text field controlled: it shows s, and after every event it shows s again unless the app rebuilt it with a new value (React's controlled inputs). Pair it with OnChange, or use Bind.
func VirtualList(count int, estimateSize func(i int) float32, renderItem func(i int) tela.Node, props ...tela.Prop) tela.NodeVirtualList is a vertical scrolling list of count items that builds, lays out and paints only the items in view, plus a few more on each side (see Overscan), so a list of 100,000 rows costs about as much as one screenful of them (TanStack Virtual's model). The node is the scrolling element itself: props style it like a Div (Class, Style, Ref, handlers, Key, Role and the Aria props), and it is overflow-y-auto unless a class sets another overflow. Give it a height (h-96, flex-1 in a column, h-screen): the items it builds are bounded by what the window shows, but only a list that scrolls by itself keeps its position stable and supports ScrollToIndex.
estimateSize(i) estimates the height of item i in logical pixels, before it is built; renderItem(i) returns it. Each item is measured once it is laid out, and the measurement replaces the estimate (per index; a new estimateSize function resets them), so items may have any height and the scrollbar, OnScroll's ScrollHeight and the aria tree reflect the whole list. When a measurement changes the size of an item above the top of the list's viewport, the scroll offset moves by the difference in the same frame: the content on screen does not jump.
Items are keyed by their index: an item component's state follows its index. renderItem runs inside the list's build, so it must not call hooks; return a component node (tela.C, tela.CP) for items with state, and prefer tela.CP so that a list rebuilt by its parent skips the items whose props did not change. Scrolling never rebuilds the items that stay in view: a scroll inside the built items only repaints, and a scroll past them builds the entering items and unmounts the leaving ones. The focused item stays mounted while it is scrolled away, so Tab goes on from it.
Scroll to an item with the ref's ScrollToIndex (tela.AlignAuto, AlignStart, AlignCenter, AlignEnd), which keeps the item aligned while the items around it are measured, and which jumps or animates like ScrollTo (scroll-smooth):
list := tela.UseElementRef()
return Div(Class("flex h-screen flex-col"),
Button(OnClick(func(tela.MouseEvent) { list.ScrollToIndex(5000, tela.AlignCenter) }),
Text("Go to 5000")),
VirtualList(100_000,
func(int) float32 { return 40 },
func(i int) tela.Node { return tela.CP(Row, i) },
Ref(list), Class("flex-1 rounded-lg border")),
)Lists are vertical; horizontal lists and grids are not virtualized yet.
Example
// A million rows cost about as much as the ones in view.
rows := VirtualList(1_000_000,
func(int) float32 { return 40 },
func(i int) tela.Node { return Div(Role("listitem"), Class("h-10"), Textf("Row %d", i)) },
Class("h-[120px]"), Overscan(0))
yaml, _ := telatest.AriaSnapshot(rows, telatest.Size(200, 120))
fmt.Println(yaml)
// Output:
// - listitem: Row 0
// - listitem: Row 1
// - listitem: Row 2