Skip to content

API Reference h

Andres Felipe Martinez Salazar edited this page Oct 14, 2026 · 10 revisions

API reference: package 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.

Index

Overview

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

Functions

func A

func A(children ...tela.Child) tela.Node

A 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

func Alt(text string) tela.Prop

Alt 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

func AriaChecked(checked bool) tela.Prop

AriaChecked sets the checked state of a checkbox, radio or switch.

Div(Role("checkbox"), AriaChecked(done), TabIndex(0), Text("Done"))

func AriaDescription

func AriaDescription(description string) tela.Prop

AriaDescription 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

func AriaDisabled(disabled bool) tela.Prop

AriaDisabled 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

func AriaExpanded(expanded bool) tela.Prop

AriaExpanded sets whether what a button or link controls (a menu, a disclosure) is expanded.

Button(AriaExpanded(open), OnClick(toggle), Text("Options"))

func AriaHidden

func AriaHidden(hidden bool) tela.Prop

AriaHidden 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

func AriaLabel(label string) tela.Prop

AriaLabel 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

func AriaLabelledBy(ref tela.ElementRef) tela.Prop

AriaLabelledBy 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

func AriaLevel(level int) tela.Prop

AriaLevel 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

func AriaPressed(pressed bool) tela.Prop

AriaPressed makes a button a toggle button and sets whether it is pressed.

Button(AriaPressed(bold), OnClick(toggleBold), Text("B"))

func AriaSelected

func AriaSelected(selected bool) tela.Prop

AriaSelected sets whether a tab, option, row or cell is selected.

Button(Role("tab"), AriaSelected(active == "general"), Text("General"))

func AriaValueMax

func AriaValueMax(value float64) tela.Prop

AriaValueMax sets the maximum value of a range widget.

func AriaValueMin

func AriaValueMin(value float64) tela.Prop

AriaValueMin sets the minimum value of a range widget.

func AriaValueNow

func AriaValueNow(value float64) tela.Prop

AriaValueNow 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

func AriaValueText(text string) tela.Prop

AriaValueText sets the value of a range widget as people read it.

Div(Role("slider"), AriaValueNow(3), AriaValueText("Medium"), track)

func AutoComplete

func AutoComplete(s string) tela.Prop

AutoComplete is HTML's autocomplete hint ("email", "current-password"...). It is accepted for familiarity; Tela has no autofill.

func AutoFocus

func AutoFocus(on bool) tela.Prop

AutoFocus 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

func Bind(text string, set tela.Setter[string]) tela.Prop

Bind 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

func BindValue(v *tela.Value[string]) tela.Prop

BindValue 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

func Button(children ...tela.Child) tela.Node

Button 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

func Class(classes string) tela.Prop

Class 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

func Code(children ...tela.Child) tela.Node

Code returns inline text in the monospace font.

func DefaultValue

func DefaultValue(s string) tela.Prop

DefaultValue 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

func Disabled(disabled bool) tela.Prop

Disabled 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

func Div(children ...tela.Child) tela.Node

Div 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

func Em(children ...tela.Child) tela.Node

Em returns italic inline text.

func For

func For(id string) tela.Prop

For makes a Label label the element whose Id is id.

func Form

func Form(children ...tela.Child) tela.Node

Form 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

func Fragment(children ...tela.Child) tela.Node

Fragment 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

func H1(children ...tela.Child) tela.Node

H1 returns a level 1 heading. Headings are unstyled: size and weight come from classes (text-5xl font-bold).

func H2

func H2(children ...tela.Child) tela.Node

H2 returns a level 2 heading. See H1.

func H3

func H3(children ...tela.Child) tela.Node

H3 returns a level 3 heading. See H1.

func H4

func H4(children ...tela.Child) tela.Node

H4 returns a level 4 heading. See H1.

func H5

func H5(children ...tela.Child) tela.Node

H5 returns a level 5 heading. See H1.

func H6

func H6(children ...tela.Child) tela.Node

H6 returns a level 6 heading. See H1.

func Href

func Href(url string) tela.Prop

Href sets the destination of an A. Tela does not open links by itself yet: handle OnClick.

func Id

func Id(id string) tela.Prop

Id gives an element an identifier, unique in the window, that a Label's For refers to.

func If

func If[C tela.Child](cond bool, c C) C

If 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: Done

func IfElse

func IfElse[C tela.Child](cond bool, a, b C) C

IfElse 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

func Img(children ...tela.Child) tela.Node

Img 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

func Input(children ...tela.Child) tela.Node

Input 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

func Key(k any) tela.Prop

Key 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

func Label(children ...tela.Child) tela.Node

Label 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

func Map[T any](items []T, f func(T) tela.Node) tela.Node

Map 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 it

func MapIndex

func MapIndex[T any](items []T, f func(i int, item T) tela.Node) tela.Node

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

func MaxLength

func MaxLength(n int) tela.Prop

MaxLength 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

func Name(s string) tela.Prop

Name names a text field or a button in its form (HTML's name).

func OnBeforeInput

func OnBeforeInput(handler func(tela.InputEvent)) tela.Prop

OnBeforeInput 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

func OnBlur(handler func(tela.FocusEvent)) tela.Prop

OnBlur 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

func OnChange(handler func(tela.ChangeEvent)) tela.Prop

OnChange 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

func OnClick(handler func(tela.MouseEvent)) tela.Prop

OnClick 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

func OnCompositionEnd(handler func(tela.CompositionEvent)) tela.Prop

OnCompositionEnd calls handler when an input method ends a composition, committed (e.Data) or canceled (""). It bubbles.

func OnCompositionStart

func OnCompositionStart(handler func(tela.CompositionEvent)) tela.Prop

OnCompositionStart calls handler when an input method (IME) starts composing text in a text field. It bubbles.

func OnCompositionUpdate

func OnCompositionUpdate(handler func(tela.CompositionEvent)) tela.Prop

OnCompositionUpdate calls handler when the text an input method composes changes (e.Data). It bubbles.

func OnContextMenu

func OnContextMenu(handler func(tela.MouseEvent)) tela.Prop

OnContextMenu 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

func OnDoubleClick(handler func(tela.MouseEvent)) tela.Prop

OnDoubleClick 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

func OnDrag(handler func(tela.DragEvent)) tela.Prop

OnDrag 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

func OnDragEnd(handler func(tela.DragEvent)) tela.Prop

OnDragEnd 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

func OnDragStart(handler func(tela.DragEvent)) tela.Prop

OnDragStart 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

func OnFocus(handler func(tela.FocusEvent)) tela.Prop

OnFocus 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

func OnKeyDown(handler func(tela.KeyboardEvent)) tela.Prop

OnKeyDown 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

func OnKeyUp(handler func(tela.KeyboardEvent)) tela.Prop

OnKeyUp 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

func OnLongPress(handler func(tela.PointerEvent)) tela.Prop

OnLongPress 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

func OnMouseDown(handler func(tela.MouseEvent)) tela.Prop

OnMouseDown calls handler when a button is pressed over the element.

Div(OnMouseDown(func(e tela.MouseEvent) {
    if e.Button == tela.MouseButtonMiddle {
        closeTab()
    }
}))

func OnMouseEnter

func OnMouseEnter(handler func(tela.MouseEvent)) tela.Prop

OnMouseEnter 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

func OnMouseLeave(handler func(tela.MouseEvent)) tela.Prop

OnMouseLeave 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

func OnMouseMove(handler func(tela.MouseEvent)) tela.Prop

OnMouseMove 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

func OnMouseUp(handler func(tela.MouseEvent)) tela.Prop

OnMouseUp calls handler when a button is released over the element.

Div(OnMouseUp(func(tela.MouseEvent) { setPressed.Set(false) }))

func OnPointerCancel

func OnPointerCancel(handler func(tela.PointerEvent)) tela.Prop

OnPointerCancel 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

func OnPointerDown(handler func(tela.PointerEvent)) tela.Prop

OnPointerDown 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

func OnPointerEnter(handler func(tela.PointerEvent)) tela.Prop

OnPointerEnter 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

func OnPointerLeave(handler func(tela.PointerEvent)) tela.Prop

OnPointerLeave 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

func OnPointerMove(handler func(tela.PointerEvent)) tela.Prop

OnPointerMove 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

func OnPointerUp(handler func(tela.PointerEvent)) tela.Prop

OnPointerUp calls handler when the last button of a pointer is released over the element.

Div(OnPointerUp(func(tela.PointerEvent) { endStroke() }))

func OnScroll

func OnScroll(handler func(tela.ScrollEvent)) tela.Prop

OnScroll 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

func OnSelect(handler func(tela.SelectEvent)) tela.Prop

OnSelect calls handler when the user selects text in a text field, or code changes its selection (e.Start, e.End). It bubbles.

func OnSubmit

func OnSubmit(handler func(tela.SubmitEvent)) tela.Prop

OnSubmit 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

func OnWheel(handler func(tela.WheelEvent)) tela.Prop

OnWheel 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

func Overscan(n int) tela.Prop

Overscan 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

func P(children ...tela.Child) tela.Node

P returns a paragraph of text and inline elements (Span, Strong, Em, Code, A).

func Placeholder

func Placeholder(s string) tela.Prop

Placeholder 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

func ReadOnly(on bool) tela.Prop

ReadOnly makes a text field read-only: it can be focused, selected and copied, but not edited; it matches read-only:.

func Ref

func Ref(ref tela.ElementRef) tela.Prop

Ref 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

func Role(role string) tela.Prop

Role 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

func Rows(n int) tela.Prop

Rows sets how many lines tall a Textarea is (2 by default).

func Span

func Span(children ...tela.Child) tela.Node

Span 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

func SpellCheck(on bool) tela.Prop

SpellCheck is HTML's spellcheck hint. It is accepted and ignored: Tela has no spell checker.

func Src

func Src(img image.Image) tela.Prop

Src 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

func Strong(children ...tela.Child) tela.Node

Strong returns bold inline text.

func Style

func Style(props ...tela.StyleProp) tela.Prop

Style 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

func TabIndex(i int) tela.Prop

TabIndex 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

func Text(s string) tela.Node

Text 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

func TextFn(fn func() string) tela.Node

TextFn 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

func Textarea(children ...tela.Child) tela.Node

Textarea 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

func Textf(format string, args ...any) tela.Node

Textf returns a text node formatted like fmt.Sprintf.

func Type

func Type(t string) tela.Prop

Type 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

func Value(s string) tela.Prop

Value 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

func VirtualList(count int, estimateSize func(i int) float32, renderItem func(i int) tela.Node, props ...tela.Prop) tela.Node

VirtualList 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

Clone this wiki locally