API reference: package aria
import "github.com/kuronosu/tela/aria"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
- Constants
- Functions:
All·AnchorRect·Arrow·AutoPlacement·Contains·DateOf·EndsWith·Flip·FormatHexColor·Hide·LimitShift·Offset·ParseHexColor·Placement·SelectionOf·Shift·Size·StartsWith·ToHSB·ToHSL·Today·UseButton·UseCalendar·UseCheckbox·UseColorArea·UseColorField·UseColorSlider·UseColorState·UseCombobox·UseContextMenuTrigger·UseDialog·UseFloating·UseFocusRing·UseFocusScope·UseHover·UseListBox·UseMenu·UseMenuBar·UseMenuTrigger·UseMeter·UseOverlay·UseOverlayTrigger·UsePopover·UsePress·UseProgressBar·UseRadioGroup·UseRangeCalendar·UseSelect·UseSlider·UseSwitch·UseTabs·UseToggleButton·UseToggleGroup·UseTooltipTrigger - Types
type ArrowOptionstype AutoPlacementOptionstype Available:MaxHeight·MaxWidthtype ButtonOptionstype ButtonResult:UseButtontype CalendarOptionstype CalendarResult:UseCalendar·UseRangeCalendar·Anchor·CellProps·ColumnHeaderProps·DayText·FirstDayOfWeek·GridProps·Heading·HeadingProps·IsDisabled·IsFocused·IsInRange·IsOutsideMonth·IsRangeEnd·IsRangeStart·IsSelected·IsToday·IsUnavailable·Month·Months·NextDisabled·NextProps·PrevDisabled·PrevProps·RowProps·WeekdayNames·Weekstype Collection:ActiveDescendant·ClearFocus·Focus·FocusKey·Focused·HandleKeyDown·IsDisabled·IsFocusVisible·IsFocused·IsSelected·ItemProps·ItemRef·Sectiontype ColorAreaOptionstype ColorAreaResult:UseColorArea·ThumbPercent·ValueTexttype ColorChanneltype ColorFieldOptionstype ColorFieldResult:UseColorFieldtype ColorSliderOptionstype ColorSliderResult:UseColorSlider·ThumbPercent·TrackColors·Value·ValueTexttype ColorState:UseColorState·Color·HSB·LiveColor·LiveHSB·Set·SetHSBtype ColorStateOptionstype ComboboxOptionstype ComboboxResult:UseComboboxtype ContextMenuTriggerOptionstype ContextMenuTriggerResult:UseContextMenuTrigger·PopoverOptionstype Date:DateOf·Today·AddDays·AddMonths·AddYears·After·Before·Compare·IsValid·IsZero·String·Time·Weekdaytype DateRange:Contains·IsZerotype DialogOptionstype DialogResult:UseDialogtype Filtertype FlipOptionstype Floating:UseFloatingtype FloatingOption:AnchorRect·Arrow·AutoPlacement·Flip·Hide·Offset·Placement·Shift·Sizetype FocusManager:FocusFirst·FocusLast·FocusNext·FocusPrevioustype FocusOptionstype FocusRingOptionstype FocusRingResult:UseFocusRingtype FocusScopeOptionstype FocusScopeResult:UseFocusScopetype HSB:ToHSB·Color·HSLtype HSL:ToHSL·Color·HSBtype HideOptionstype HoverEventtype HoverOptionstype HoverResult:UseHovertype LimitShiftOptionstype ListBoxOptionstype ListBoxResult:UseListBoxtype MenuBarOptionstype MenuBarResult:UseMenuBar·IsOpen·MenuProps·MenuRef·PopoverOptions·SetOpen·TriggerProps·TriggerReftype MenuOptionstype MenuResult:UseMenutype MenuTriggerOptionstype MenuTriggerResult:UseMenuTriggertype MeterOptionstype OffsetOptionstype OverlayOptionstype OverlayResult:UseOverlaytype OverlayTriggerOptionstype OverlayTriggerResult:UseOverlayTriggertype PointerTypetype PopoverOptionstype PopoverResult:UsePopovertype PressEvent:ContinuePropagationtype PressOptionstype PressResult:UsePresstype ProgressBarOptionstype ProgressBarResult:UseMeter·UseProgressBartype RadioGroupOptionstype RadioGroupResult:UseRadioGroup·IsSelected·RadioID·RadioProps·RadioReftype RangeCalendarOptionstype Rectstype SectionResulttype SelectOptionstype SelectResult:UseSelecttype Selection:All·SelectionOf·Equal·First·Has·In·IsAll·IsEmpty·IsSet·Keys·Len·Resolve·String·Toggle·With·Withouttype ShiftLimiter:LimitShifttype ShiftOptionstype SizeOptionstype SliderOptionstype SliderResult:UseSlider·IsThumbDragging·ThumbPercent·ThumbProps·ThumbRef·ValueTexttype TabsOptionstype TabsResult:UseTabs·PanelProps·TabPropstype ToggleGroupOptionstype ToggleGroupResult:UseToggleGroup·IsSelected·ItemProps·ItemReftype ToggleOptionstype ToggleResult:UseCheckbox·UseSwitch·UseToggleButtontype TooltipTriggerOptionstype TooltipTriggerResult:UseTooltipTrigger
Overview
Section titled “Overview”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. It follows React Aria (Adobe’s behavior hooks, Apache-2.0): the same names, the same rules for pointers, keys and focus, translated to Tela’s hooks and props. The styled components of package kit are built on it; use it directly to build your own.
The pattern: options in, Props out
Section titled “The pattern: options in, Props out”A hook takes an options struct and returns a struct. Its Props field is one tela.Prop that carries everything the hook needs on the element: event handlers, the ref, the role and the aria states. Give it to the element, and style the element from the other fields of the result:
press := aria.UsePress(aria.PressOptions{ OnPress: func(e aria.PressEvent) { save() }, Disabled: busy,})return Button(press.Props, Class("rounded-md px-3 py-1.5"), If(press.Pressed, Class("scale-95")), Text("Save"))Props merges several props into one (core’s MergeProps): the handlers of one event all run, in the order given, and the others (a class, a role) follow the same rules as props given one after the other. So hook props combine with your own props and with each other:
ref := tela.UseElementRef() // see "One element, several hooks" belowbtn := aria.UseButton(aria.ButtonOptions{Ref: ref})hover := aria.UseHover(aria.HoverOptions{})ring := aria.UseFocusRing(aria.FocusRingOptions{Ref: ref})return Button(btn.Props, hover.Props, ring.Props, Class("rounded-md px-3 py-1.5 transition"), If(hover.Hovered, Class("bg-accent")), If(ring.FocusVisible, Class("ring-2 ring-primary")), ...)The hooks are ordinary custom hooks (ADR-085): call them at the top level of a component, in the same order on every build.
One element, several hooks: the ref
Section titled “One element, several hooks: the ref”A few hooks need the element itself (press measures it to know when the pointer leaves, the focus ring asks the engine who has the focus) and attach a ref to it. An element has one ref, so when several hooks serve one element they must share it: make it once with tela.UseElementRef and give it to each hook as its Ref option. The hooks that need no ref (UseHover, UseOverlay) take none.
ref := tela.UseElementRef()btn := aria.UseButton(aria.ButtonOptions{Ref: ref, OnPress: func(aria.PressEvent) { save() }})ring := aria.UseFocusRing(aria.FocusRingOptions{Ref: ref})return Button(btn.Props, ring.Props, ...)Two different refs on one element are a mistake that dev mode reports with a panic that names both call sites and this fix (outside dev mode the last one wins). That includes h.Ref(other) next to the props of a hook: use the Ref field of the hook’s result instead, or pass the ref to the hook. The same ref twice is fine.
Controlled and uncontrolled state
Section titled “Controlled and uncontrolled state”A hook that holds state keeps it itself by default (uncontrolled), and lets the caller keep it (controlled) when the caller passes a pointer to the value, next to the callback that asks for changes. The callback is also called for an uncontrolled control, to follow it:
// Uncontrolled: DefaultSelected is the initial state.box := aria.UseCheckbox(aria.ToggleOptions{DefaultSelected: true})// Uncontrolled and notified: read the result's Selected, or listen.box = aria.UseCheckbox(aria.ToggleOptions{OnChange: func(on bool) { save(on) }})// Controlled: the caller keeps the state.on, setOn := tela.UseState(false)sw := aria.UseSwitch(aria.ToggleOptions{Selected: &on, OnChange: setOn.Set})A controlled control shows what the caller passes: a change that the caller does not store is refused, as with any controlled field. The open state of the overlay hooks (OverlayTriggerOptions, MenuTriggerOptions and TooltipTriggerOptions) follows the same rule with Open, DefaultOpen and OnOpenChange.
Interactions: UsePress (mouse, touch, pen and keyboard as one press), UseHover (mouse and pen) and UseFocusRing (the focus, and whether to show it). Focus: UseFocusScope (contain, restore and auto-focus the focus, and a FocusManager that moves it). Controls: UseButton (which can submit or reset its Form), UseToggleButton, UseCheckbox and UseSwitch, which add the role, the aria state, the tab stop and the toggle state to a press. Choices and ranges: UseRadioGroup (one checked radio, which the arrow keys move and check), UseToggleGroup (a segmented control or a toolbar of toggle buttons), UseSlider (one or more thumbs on a track, dragged by repainting), UseProgressBar and UseMeter. Dates: UseCalendar and UseRangeCalendar, with the Date type. Colors: UseColorState (the color that a picker shares, as an HSB that keeps the hue), UseColorArea, UseColorSlider and UseColorField, with the HSB and HSL types, ToHSB, ToHSL, ParseHexColor and FormatHexColor. Positioning: UseFloating.
Overlays: UseOverlay (Escape and a press outside close the top overlay; a modal one makes the rest of the window inert), UseDialog (role, name and focus), UsePopover (a floating overlay next to its trigger), UseOverlayTrigger (the open state and the trigger’s props), UseMenuTrigger (a trigger that opens with press, long press or keys) and UseTooltipTrigger (hover and focus with React Aria’s delays and warm-up).
Collections, keyed lists of items with a focused key and a selection: UseListBox, UseMenu (the menu of UseMenuTrigger), UseTabs, UseSelect (a button that opens a list box) and UseCombobox (a text field that filters one, with the Contains and StartsWith filters), with the Selection type for what is selected.
Overlays
Section titled “Overlays”An overlay is rendered by its owner, in a Portal and only while it is open; the hooks give the props. The trigger hook holds the state, the popover positions and dismisses, the dialog names and focuses, and they share the overlay element’s ref:
trig := aria.UseOverlayTrigger(aria.OverlayTriggerOptions{})pop := aria.UsePopover(aria.PopoverOptions{ Trigger: trig.Ref, Ref: trig.OverlayRef, Open: trig.Open, OnClose: trig.Close, Placement: "bottom-start", Offset: 4,})dlg := aria.UseDialog(aria.DialogOptions{Ref: trig.OverlayRef, AriaLabel: "Filters"})return Fragment( Button(trig.TriggerProps, Text("Filters")), Portal(Presence(If(trig.Open, Div(pop.Props, dlg.Props, Exit("opacity-0"), Class("rounded-md border bg-popover p-4 transition starting:opacity-0"), ...)))))To let an overlay fade out, render it inside a Presence with If and give it Exit classes and a transition (and the starting: variant for the entrance), as above. An exiting overlay is inert and off the overlay stack at once, so the page below works while it fades, a second Escape reaches the next overlay, and the hooks’ Open value is already false.
Presses and Escape reach only the top overlay. What is opened from an overlay is inside it (a popover in a dialog, a tooltip on a button in a popover), in a Portal or not.
Collections
Section titled “Collections”A list box, a menu and a tab list are collections: the caller gives the keys of the items (any comparable type) on every build, and renders one element per key with the props of ItemProps(key). The hook keeps the focused key and the selection, moves the focus with the arrow keys, Home, End, PageUp, PageDown and typeahead, and selects with the pointer and the keyboard as React Aria does.
lb := aria.UseListBox(aria.ListBoxOptions[string]{Keys: names, SelectionMode: "multiple"})return Div(lb.Props, Range(len(names), func(i int) tela.Node { return Div(lb.ItemProps(names[i]), Key(names[i]), Class("aria-selected:bg-accent focus-visible:ring-2"), Text(names[i]))}))The selection is a Selection, uncontrolled by default and controlled when the options hold a non-zero one:
sel, setSel := tela.UseState(aria.SelectionOf("b"))aria.ListBoxOptions[string]{Keys: names, SelectionMode: "multiple", Selected: sel, OnSelectionChange: setSel.Set}The focus is roving (the focused item’s element has it) unless ShouldUseVirtualFocus keeps it on the collection and marks the focused item with aria-activedescendant. Give the items of a long list to a h.VirtualList: keys that are not rendered can still be focused.
A select and a combobox put a list box in a popover next to a trigger or an input. They return the props of each of their elements; the popover goes in a Portal, only while Open, and the items are rendered from ListBox.ItemProps as in a list box:
sel := aria.UseSelect(aria.SelectOptions[string]{Keys: fruit, Placeholder: "Pick one"})cb := aria.UseCombobox(aria.ComboboxOptions[string]{Items: fruit, AriaLabel: "Fruit"})Radio groups, toggle groups and sliders
Section titled “Radio groups, toggle groups and sliders”A radio group keeps one checked key, uncontrolled by default and controlled when Value points to the caller’s state; the caller renders a Button per key with RadioProps(key), and a Label(For(RadioID(key))) names and checks it:
g := aria.UseRadioGroup(aria.RadioGroupOptions[string]{Keys: plans, AriaLabel: "Plan"})return Div(g.Props, Range(len(plans), func(i int) tela.Node { return Button(g.RadioProps(plans[i]), Key(plans[i]), Class("checked:bg-primary"), Text(plans[i]))}))A toggle group is the same with a Selection: SelectionMode “single” (a radio group of toggle buttons) or “multiple” (a toolbar of aria-pressed buttons). A slider has one thumb per value; its ThumbPercent is read in a paint-phase closure, so that dragging repaints and does not build:
s := aria.UseSlider(aria.SliderOptions{DefaultValues: []float64{20, 80}, AriaLabel: "Price"})track := tela.UseSize(s.TrackRef)return Div(s.Props, Div(s.TrackProps, Class("relative h-2 w-64 bg-muted"), Range(2, func(i int) tela.Node { return Div(s.ThumbProps(i), Key(i), Class("absolute size-5 -ml-2.5 rounded-full bg-primary"), Style(tela.TranslateFn(func() (x, y float32) { return float32(s.ThumbPercent(i)) / 100 * track.Get().Width, 0 }))) })))Calendars
Section titled “Calendars”A calendar selects a date (or, with UseRangeCalendar, a range of dates) in a month grid, with React Aria’s keys and rules, in the Gregorian calendar and the locale of package i18n (month names, weekday names, the first day of the week). A day is a Date, a comparable value with no time zone; the zero Date is none. The caller renders the parts from the methods of the result: the heading and month buttons, the weekday headers, and one grid of Weeks per visible month with a CellProps per day, keyed by its date, and the grid keyed by its month:
cal := aria.UseCalendar(aria.CalendarOptions{AriaLabel: "Birthday", Min: aria.Today()})return Div(cal.Props, Button(cal.PrevProps(), Text("<")), H2(cal.HeadingProps(0), Text(cal.Heading(0))), Button(cal.NextProps(), Text(">")), Div(cal.GridProps(0), Key(cal.Month(0).String()), Map(cal.Weeks(0), func(week []aria.Date) tela.Node { return Div(cal.RowProps(), Key(week[0].String()), Class("flex"), Map(week, func(day aria.Date) tela.Node { return Button(cal.CellProps(0, day), Key(day.String()), Text(cal.DayText(day))) })) })))The selected date is uncontrolled by default and controlled when Value points to the caller’s state, as everywhere in this package; so is the focused date, which decides the month on display.
Colors
Section titled “Colors”A color picker is a UseColorState that its parts share: an area (UseColorArea: saturation along one axis, brightness along the other), sliders for the hue and the opacity (UseColorSlider, whose TrackColors are the stops of the gradient behind the thumb) and a hex field (UseColorField). The state is the color the picker shows, uncontrolled by default and controlled when Value points to the caller’s tela.Color; it keeps the color as an HSB, so that dragging through gray or black does not lose the hue. All the parts are made in one component, and dragging moves the thumbs by repainting, as a slider does:
st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(tela.Hex("#6366f1"))})area := aria.UseColorArea(aria.ColorAreaOptions{State: st, AriaLabel: "Color"})hue := aria.UseColorSlider(aria.ColorSliderOptions{State: st, Channel: aria.ColorHue})hex := aria.UseColorField(aria.ColorFieldOptions{State: st, AriaLabel: "Hex"})Testing and agents
Section titled “Testing and agents”The roles and states the hooks set are the ones the aria snapshot shows: a checkbox made with UseCheckbox prints as checkbox "Name" [checked], and telatest finds it with ByRole(“checkbox”, “Name”).
Constants
Section titled “Constants”const ( // MenuTriggerInput opens the list when the user types (the default). MenuTriggerInput = "input" // MenuTriggerFocus opens it when the input gets the focus too, with // every item. MenuTriggerFocus = "focus" // MenuTriggerManual opens it only with ArrowDown, ArrowUp and the // toggle button. MenuTriggerManual = "manual")The ways a combobox’s menu opens (ComboboxOptions.MenuTrigger).
const ( // FocusFirst focuses the first item: the menu opened with the keyboard // (ArrowDown, Enter, Space), a long press or a virtual click. FocusFirst = "first" // FocusLast focuses the last item: the menu opened with ArrowUp. FocusLast = "last")Where the focus goes when a menu opens (MenuTriggerResult.FocusStrategy).
const ( // DefaultTooltipDelay is TooltipTriggerOptions.Delay when it is zero. DefaultTooltipDelay = 1500 * time.Millisecond // DefaultTooltipCloseDelay is TooltipTriggerOptions.CloseDelay when it // is zero; it is also how long after the last tooltip closes the next // one still opens at once (the warm-up). DefaultTooltipCloseDelay = 500 * time.Millisecond // NoDelay as a delay of [TooltipTriggerOptions] opens or closes the // tooltip at once (the zero value stands for the default delay). NoDelay time.Duration = -1)The tooltip delays of React Aria: a tooltip opens 1.5 s after the pointer enters its trigger, and closes 0.5 s after it leaves.
Functions
Section titled “Functions”func Contains
Section titled “func Contains”func Contains(text, query string) boolContains reports whether text contains query anywhere, ignoring case and diacritics (“cafe” matches “Café”; React Aria’s useFilter with the “base” sensitivity of its collator). An empty query matches everything.
Example
fmt.Println(aria.Contains("São Paulo", "ao p"))fmt.Println(aria.StartsWith("Zürich", "ZUR"))fmt.Println(aria.EndsWith("Zürich", "ch"), aria.EndsWith("Zürich", "ürix"))// Output:// true// true// true falsefunc EndsWith
Section titled “func EndsWith”func EndsWith(text, query string) boolEndsWith reports whether text ends with query, ignoring case and diacritics. An empty query matches everything.
func FormatHexColor
Section titled “func FormatHexColor”func FormatHexColor(c tela.Color, alpha bool) stringFormatHexColor returns c as “#rrggbb”, in lower case with the components rounded to 8 bits and clamped to the sRGB gamut; with alpha it is “#rrggbbaa”. ParseHexColor reads it back.
func ParseHexColor
Section titled “func ParseHexColor”func ParseHexColor(s string) (tela.Color, error)ParseHexColor parses a hex color as people type it: “#rgb”, “#rgba”, “#rrggbb” or “#rrggbbaa”, in any case, with or without the leading “#” and with surrounding spaces ignored. The error says what is wrong and is meant to be shown (or the input reverted).
c, err := aria.ParseHexColor("#1e1e2e")Example
c, err := aria.ParseHexColor("1E1E2E")fmt.Println(aria.FormatHexColor(c, false), err)_, err = aria.ParseHexColor("#12345")fmt.Println(err)// Output:// #1e1e2e <nil>// aria: invalid hex color "#12345": expected 3, 4, 6 or 8 hex digits (#rgb, #rgba, #rrggbb or #rrggbbaa), got 5func StartsWith
Section titled “func StartsWith”func StartsWith(text, query string) boolStartsWith reports whether text starts with query, ignoring case and diacritics. An empty query matches everything.
type ArrowOptions
Section titled “type ArrowOptions”type ArrowOptions struct { // Padding keeps the arrow that many pixels from the corners of the // floating element (Floating UI's padding). Padding float32}ArrowOptions are the options of Arrow.
type AutoPlacementOptions
Section titled “type AutoPlacementOptions”type AutoPlacementOptions struct { // Padding is the space to keep between the element and the edges of the // area it must stay in. Padding float32 // CrossAxis also counts the overflow on the alignment axis when it // chooses among aligned placements. CrossAxis bool // Alignment restricts the choice to placements with that alignment // ("start" or "end"). Alignment string // AllowedPlacements are the placements it chooses among; nil is all. AllowedPlacements []string // NoAutoAlignment stops it from choosing the other alignment when // Alignment is set. NoAutoAlignment bool}AutoPlacementOptions are the options of AutoPlacement; the zero value is Floating UI’s defaults.
type Available
Section titled “type Available”type Available struct { // Width and Height are the largest size the floating element's border // box can take without leaving the area it must stay in, on the side of // the anchor it is on (Floating UI's availableWidth and availableHeight). // Negative when there is no room at all. Width, Height float32 // Rects are the rectangles of the anchor and the floating element. Rects Rects // Placement is the placement the size is computed for. Placement string // contains filtered or unexported fields}Available is what Size gives its apply function: the room the floating element has and the means to limit it.
func (Available) MaxHeight
Section titled “func (Available) MaxHeight”func (a Available) MaxHeight(h float32)MaxHeight limits the floating element’s border-box height to h, like Available.MaxWidth. An element with overflow-y-auto then scrolls its content.
func (Available) MaxWidth
Section titled “func (Available) MaxWidth”func (a Available) MaxWidth(w float32)MaxWidth limits the floating element’s border-box width to w: the smaller of w and the element’s own max-width wins, and the element is laid out again within the frame. Not calling it removes the limit.
type ButtonOptions
Section titled “type ButtonOptions”type ButtonOptions struct { // OnPress is called when the button is pressed and released over it, // with Enter, with Space, or by a virtual click. OnPress func(PressEvent) // OnPressStart is called when a press begins. OnPressStart func(PressEvent) // OnPressEnd is called when a press ends, whether it counts or not. OnPressEnd func(PressEvent) // OnPressChange is called with true when a press starts and false when // it ends. OnPressChange func(pressed bool) // OnPressUp is called when the pointer or the key is released over the // button. OnPressUp func(PressEvent) // Disabled makes the button inert and disabled for styling and // assistive technologies (h.Disabled): it cannot be focused or // pressed, and the `disabled:` variants apply. Disabled bool // ShouldCancelOnPointerExit ends the press for good when the pointer // leaves the button. ShouldCancelOnPointerExit bool // PreventFocusOnPress keeps the focus where it is when the button is // pressed with a pointer. PreventFocusOnPress bool // Type is what pressing the button does to the Form that contains it, // as HTML's type attribute: "button" (the default) nothing, // "submit" submits the Form (OnSubmit) and "reset" resets it, after // OnPress has run. A press has this effect whatever the element is, a // Button or a Div, and so does ElementRef.Click on it. Another value // panics in dev mode with the closest valid one. Type string // Ref is the element's ref, for a hook that is used together with // others on one element (see the package documentation); the zero // value makes the hook create its own. Ref tela.ElementRef}ButtonOptions configures UseButton. The press fields are those of PressOptions.
type ButtonResult
Section titled “type ButtonResult”type ButtonResult struct { // Props go to the element: everything [UsePress] returns, the // "button" role, a tab stop and the disabled state. Props tela.Prop // Pressed is true while the button is pressed. Pressed bool // Ref is the ref attached to the element by Props. Ref tela.ElementRef}ButtonResult is what UseButton and UseToggleButton return.
func UseButton
Section titled “func UseButton”func UseButton(o ButtonOptions) ButtonResultUseButton returns the props that make an element a button (React Aria’s useButton): UsePress plus the “button” role, a tab stop and the disabled state. It works on a Button element and on any other, such as a Div or an A, that should behave as one.
btn := aria.UseButton(aria.ButtonOptions{OnPress: func(aria.PressEvent) { save() }})return Button(btn.Props, Class("rounded-md bg-primary px-3 py-1.5 text-primary-foreground"), If(btn.Pressed, Class("scale-95")), Text("Save"))A Button that has the hook’s props presses with Enter and Space. Like every press, it prevents the default action of the click, so the Form is not submitted or reset by the element’s own activation: set Type to “submit” or “reset” and the hook does it when the press completes, on a Button or on any other element.
save := aria.UseButton(aria.ButtonOptions{Type: "submit"})return Form(OnSubmit(func(tela.SubmitEvent) { send() }), Div(save.Props, Class("rounded-md bg-primary px-3 py-1.5"), Text("Send")))Example
t := &exampleT{}defer t.close()page := func() tela.Node { clicks, setClicks := tela.UseState(0) btn := aria.UseButton(aria.ButtonOptions{ OnPress: func(aria.PressEvent) { setClicks.Update(func(n int) int { return n + 1 }) }, }) // A Div that is a button: role, tab stop and the press come with the hook. return Div(btn.Props, Class("rounded-md bg-primary px-3 py-1.5 text-primary-foreground"), If(btn.Pressed, Class("scale-95")), Textf("Clicked %d", clicks))}app := telatest.MountFunc(t, page)app.Click(telatest.ByRole("button", "Clicked 0"))fmt.Println(app.AriaSnapshot())// Output: - button "Clicked 1"Example (submit)
// A Div made a button that submits its Form, as an HTML submit button// does.page := func() tela.Node { sent, setSent := tela.UseState(0) send := aria.UseButton(aria.ButtonOptions{Type: "submit"}) return Form(OnSubmit(func(tela.SubmitEvent) { setSent.Update(func(n int) int { return n + 1 }) }), Div(send.Props, Class("rounded-md bg-primary px-3 py-1.5"), Text("Send")), Textf("Sent %d times", sent))}app := telatest.MountFunc(&exampleTB{}, page)app.Click(telatest.ByRole("button", "Send"))app.Press("Enter")app.ExpectText("Sent 2 times")fmt.Println("submitted twice: mouse and Enter")// Output: submitted twice: mouse and Entertype CalendarOptions
Section titled “type CalendarOptions”type CalendarOptions struct { // Value points to the selected date of a controlled calendar; nil // leaves it to the hook. Value *Date // DefaultValue is the selected date of an uncontrolled calendar at the // start; the zero Date selects nothing. DefaultValue Date // OnValueChange is called with the date the user selects: how a // controlled calendar asks for the change, a notification for an // uncontrolled one. It is not called for the date that already is // selected. OnValueChange func(Date) // Min and Max bound the dates that can be focused or selected (both // included); the zero Date is no limit. Dates outside them are shown // disabled, and the buttons of PrevProps and NextProps are disabled // when no month is left in that direction. Min after Max panics in // dev mode. Min, Max Date // IsDateUnavailable marks dates that are shown but can not be // selected (booked days, holidays). As in React Aria they can still // be focused: the keys move over them and they have aria-disabled. IsDateUnavailable func(Date) bool // Disabled disables every date: none can be focused or selected. Disabled bool // ReadOnly keeps the dates focusable but not selectable (aria-readonly // on the grid). ReadOnly bool // FocusedDate points to the focused date of a controlled calendar, // the one with the tab stop; the month that shows it is on display. // nil leaves it to the hook. FocusedDate *Date // DefaultFocusedDate is the focused date of an uncontrolled calendar at // the start: else the selected date, else Today. It is limited to Min // and Max. DefaultFocusedDate Date // OnFocusChange is called with the date that becomes the focused one: // by keys, by focus, by a month button, by a pointer that previews a // range. For a controlled calendar it is how the change is asked for. OnFocusChange func(Date) // VisibleMonths is the number of months shown at once: 1 (the // default) to 12. The month buttons move by that many. VisibleMonths int // FirstDayOfWeek is the first column of the grid; nil takes the // locale's (i18n.FirstDayOfWeek: Sunday for en-US, Monday for de, // Saturday for ar-EG). FirstDayOfWeek *time.Weekday // FixedWeeks always shows six weeks per month, so the grid keeps its // height from month to month; else a month has the weeks it needs. FixedWeeks bool // Today is the date [CalendarResult.IsToday] reports; the zero Date is // the current day (aria.Today). Set it to make a test deterministic. Today Date // Locale is the BCP 47 tag of the names, the headings and the first // weekday; empty is the component's locale (i18n.UseFormatter). Locale string // Direction is "ltr" or "rtl": it swaps ArrowLeft and ArrowRight. Empty // is the locale's. Direction string // AriaLabel names the calendar ("Appointment date"); the visible range // ("October 2026") is appended, as React Aria does. Else AriaLabelledBy // names it. AriaLabel string AriaLabelledBy tela.ElementRef // PreviousLabel and NextLabel are the accessible names of the month // buttons, "Previous" and "Next" by default. They are English unless // the caller translates them (with i18n.T). PreviousLabel, NextLabel string // Ref is the calendar element's ref, for a hook that is used together // with others on one element; the zero value makes the hook create // its own. Ref tela.ElementRef}CalendarOptions configures UseCalendar. Dates are Date values, the zero Date meaning none.
The selected date is uncontrolled when Value is nil (the hook keeps it, starting at DefaultValue, or with nothing selected) and controlled when Value points to the caller’s state: the calendar shows it and only asks OnValueChange for the next one, so a change the caller does not store is refused. OnValueChange is also called for an uncontrolled calendar. The focused date, which decides the month on show and the tab stop, follows the same rule with FocusedDate, DefaultFocusedDate and OnFocusChange.
// Uncontrolled.aria.CalendarOptions{AriaLabel: "Appointment date"}// Controlled.day, setDay := tela.UseState(aria.Date{})aria.CalendarOptions{Value: &day, OnValueChange: setDay.Set, Min: aria.Today()}type CalendarResult
Section titled “type CalendarResult”type CalendarResult struct { // Props go to the calendar's element: the "application" role, its name // and the ref (React Aria's calendarProps). Props tela.Prop // Title is the heading of the whole visible range, month and year in // the locale's order ("October 2026", "October 2026 – November 2026" // with two months): what an app shows above a single grid, or announces. Title string // Value is the selected date of a single-date calendar, the zero Date // for none. Range is the selected range of a range calendar (while the // first date of a new one is chosen it is still the previous range). Value Date Range DateRange // FocusedDate is the date with the tab stop. FocusedDate Date // Ref is the ref attached to the calendar by Props. Ref tela.ElementRef // contains filtered or unexported fields}CalendarResult is what UseCalendar and UseRangeCalendar return. Its methods give the props and the data of each part of the calendar: the heading and the month buttons, the weekday headers, and one grid of weeks of cells per visible month (see UseCalendar for how they fit together).
func UseCalendar
Section titled “func UseCalendar”func UseCalendar(o CalendarOptions) CalendarResultUseCalendar holds the state of a calendar that selects one date and returns the props and data of its parts (React Aria’s useCalendar, useCalendarGrid and useCalendarCell with react-stately’s useCalendarState). The calendar is the Gregorian one; a date is a Date, and the names, the headings and the first day of the week come from package i18n.
The caller renders the calendar. Each part takes its props from the result. The cells are keyed by their date and each grid by its month, so that a month on display is a new grid whose cell for the focused date takes the focus as it mounts (a calendar without those keys still works: the hook focuses the cell after the build):
cal := aria.UseCalendar(aria.CalendarOptions{AriaLabel: "Birthday"})return Div(cal.Props, Class("w-64 p-3"), Div(Class("flex items-center justify-between"), Button(cal.PrevProps(), Text("<")), H2(cal.HeadingProps(0), Text(cal.Heading(0))), Button(cal.NextProps(), Text(">"))), Div(cal.GridProps(0), Key(cal.Month(0).String()), Div(cal.RowProps(), Class("grid grid-cols-7"), MapIndex(cal.WeekdayNames(i18n.NameShort), func(i int, name string) tela.Node { return Div(cal.ColumnHeaderProps(i), Key(name), Text(name)) })), Map(cal.Weeks(0), func(week []aria.Date) tela.Node { return Div(cal.RowProps(), Key(week[0].String()), Class("grid grid-cols-7"), Map(week, func(day aria.Date) tela.Node { return Button(cal.CellProps(0, day), Key(day.String()), Class("aria-selected:bg-primary aria-selected:text-primary-foreground"), If(cal.IsOutsideMonth(0, day), Class("opacity-50")), Text(cal.DayText(day))) })) })))A cell is one element, a focusable gridcell (the ARIA grid pattern): use a Button, which gives it a focus ring, and style the states from the aria-selected:, aria-disabled: and focus-visible: variants or from the queries of the result (CalendarResult.IsToday, CalendarResult.IsUnavailable).
Keys, on a cell: ArrowLeft and ArrowRight move the focus a day (swapped by a right-to-left Direction), ArrowUp and ArrowDown a week, PageUp and PageDown a month and with Shift a year (the day of the month is kept, or the last one), Home and End the first and last day of the week, Enter and Space select the date. The month on display follows the focus. Dates outside Min and Max can not be reached (the focus stops at the limit); unavailable dates can, and are not selectable.
Example
t := &exampleT{}defer t.close()var picked aria.Datepage := func() tela.Node { cal := aria.UseCalendar(aria.CalendarOptions{ AriaLabel: "Appointment", Locale: "en-US", Today: aria.Date{Year: 2026, Month: time.October, Day: 25}, OnValueChange: func(day aria.Date) { picked = day }, }) return Div(cal.Props, Class("w-64 p-3"), Div(Class("flex items-center justify-between"), Button(cal.PrevProps(), Text("<")), H2(cal.HeadingProps(0), Text(cal.Heading(0))), Button(cal.NextProps(), Text(">"))), Div(cal.GridProps(0), Div(cal.RowProps(), Class("flex"), MapIndex(cal.WeekdayNames(i18n.NameShort), func(i int, name string) tela.Node { return Div(cal.ColumnHeaderProps(i), Key(name), Class("w-8"), Text(name)) })), Map(cal.Weeks(0), func(week []aria.Date) tela.Node { return Div(cal.RowProps(), Key(week[0].String()), Class("flex"), Map(week, func(day aria.Date) tela.Node { return Button(cal.CellProps(0, day), Key(day.String()), Class("size-8 aria-selected:bg-primary aria-disabled:opacity-40"), If(cal.IsOutsideMonth(0, day), Class("text-muted-foreground")), Text(cal.DayText(day))) })) })))}app := telatest.MountFunc(t, page, telatest.Size(300, 300))// Tab reaches the grid at today's cell; the arrows move over the months,// PageDown a month, Enter selects.app.Focus(telatest.ByRole("button", "Next"))app.Press("Tab")app.Press("ArrowRight")app.Press("PageDown")app.Press("Enter")title, _, _ := strings.Cut(app.AriaSnapshot(), "\n")fmt.Println(picked, title)// Output:// 2026-11-26 - application "Appointment, November 2026":func UseRangeCalendar
Section titled “func UseRangeCalendar”func UseRangeCalendar(o RangeCalendarOptions) CalendarResultUseRangeCalendar is UseCalendar for a range of dates (React Aria’s useRangeCalendar with useRangeCalendarState). Selecting works in two steps: a press or Enter on a date anchors the range, and while it is anchored the pointer over a date, or the focus on it, previews the range between the anchor and that date (see CalendarResult.IsInRange); a second press completes the range, whichever end comes first, and calls OnValueChange. Escape cancels an anchored range. While anchored, dates beyond the nearest unavailable one are not selectable, unless AllowsNonContiguousRanges. The result is the same as a single calendar’s; its Range is the selected range, and CalendarResult.Anchor the first date of a range being chosen.
Example
t := &exampleT{}defer t.close()var stay aria.DateRangepage := func() tela.Node { cal := aria.UseRangeCalendar(aria.RangeCalendarOptions{ AriaLabel: "Stay", Locale: "en-US", Today: aria.Date{Year: 2026, Month: time.October, Day: 25}, OnValueChange: func(r aria.DateRange) { stay = r }, }) return Div(cal.Props, Div(cal.GridProps(0), Map(cal.Weeks(0), func(week []aria.Date) tela.Node { return Div(cal.RowProps(), Key(week[0].String()), Class("flex"), Map(week, func(day aria.Date) tela.Node { return Button(cal.CellProps(0, day), Key(day.String()), Class("size-8 aria-selected:bg-primary"), Text(cal.DayText(day))) })) })))}app := telatest.MountFunc(t, page, telatest.Size(300, 300))// The first press anchors, moving the focus previews, the second press// completes the range.app.Focus(telatest.ByRole("gridcell", "Sunday, October 25, 2026"))app.Press("Enter")app.Press("ArrowDown")app.Press("ArrowRight")app.Press("Enter")fmt.Println(stay.Start, stay.End)// Output:// 2026-10-25 2026-11-02func (CalendarResult) Anchor
Section titled “func (CalendarResult) Anchor”func (r CalendarResult) Anchor() (Date, bool)Anchor returns the first date of a range being chosen, and whether there is one.
func (CalendarResult) CellProps
Section titled “func (CalendarResult) CellProps”func (r CalendarResult) CellProps(i int, d Date) tela.PropCellProps returns the props of the cell of date d in the grid of month i (React Aria’s useCalendarCell): the “gridcell” role, a name that is the full localized date (“Monday, October 5, 2026”), aria-selected, the disabled state, the ref, the tab stop (the focused date’s cell in its own month only) and the handlers: a click or tap selects, the keys of UseCalendar move the focus, and a pointer over a cell previews the range being chosen. A date outside Min and Max or a disabled calendar has the Disabled prop (it can not be focused); an unavailable one, aria-disabled only. Render the cell with a Button and give it Key(d.String()): the cell of the focused date is a new element when the month changes and takes the focus when it mounts.
func (CalendarResult) ColumnHeaderProps
Section titled “func (CalendarResult) ColumnHeaderProps”func (r CalendarResult) ColumnHeaderProps(i int) tela.PropColumnHeaderProps returns the props of the header of column i (0 to 6): the “columnheader” role, named by the full weekday (“Monday”) whatever the short text it shows.
func (CalendarResult) DayText
Section titled “func (CalendarResult) DayText”func (r CalendarResult) DayText(d Date) stringDayText returns the day of the month of d as the locale writes numbers (“5”, “٥”).
func (CalendarResult) FirstDayOfWeek
Section titled “func (CalendarResult) FirstDayOfWeek”func (r CalendarResult) FirstDayOfWeek() time.WeekdayFirstDayOfWeek returns the first column’s weekday.
func (CalendarResult) GridProps
Section titled “func (CalendarResult) GridProps”func (r CalendarResult) GridProps(i int) tela.PropGridProps returns the props of the grid of month i: the “grid” role, named by its heading, aria-readonly and, for a range calendar, aria-multiselectable.
func (CalendarResult) Heading
Section titled “func (CalendarResult) Heading”func (r CalendarResult) Heading(i int) stringHeading returns the title of month i, month and year in the locale’s order and form (“October 2026”, “octubre de 2026”, “2026年10月”).
func (CalendarResult) HeadingProps
Section titled “func (CalendarResult) HeadingProps”func (r CalendarResult) HeadingProps(i int) tela.PropHeadingProps go to the element that shows CalendarResult.Heading of month i (an H2): the grid of that month is named by it.
func (CalendarResult) IsDisabled
Section titled “func (CalendarResult) IsDisabled”func (r CalendarResult) IsDisabled(d Date) boolIsDisabled reports whether d can not be focused: the calendar is Disabled, or d is before Min or after Max.
func (CalendarResult) IsFocused
Section titled “func (CalendarResult) IsFocused”func (r CalendarResult) IsFocused(d Date) boolIsFocused reports whether d is the focused date and the focus is inside the calendar.
func (CalendarResult) IsInRange
Section titled “func (CalendarResult) IsInRange”func (r CalendarResult) IsInRange(d Date) boolIsInRange is CalendarResult.IsSelected for a range calendar: whether d is in the range, its ends included; it is false for a single-date calendar. Style the ends with IsRangeStart and IsRangeEnd.
func (CalendarResult) IsOutsideMonth
Section titled “func (CalendarResult) IsOutsideMonth”func (r CalendarResult) IsOutsideMonth(i int, d Date) boolIsOutsideMonth reports whether d is not a day of month i on display: a date of the neighbouring months that fills the first or last week.
func (CalendarResult) IsRangeEnd
Section titled “func (CalendarResult) IsRangeEnd”func (r CalendarResult) IsRangeEnd(d Date) boolIsRangeEnd reports whether d is the last date of the range.
func (CalendarResult) IsRangeStart
Section titled “func (CalendarResult) IsRangeStart”func (r CalendarResult) IsRangeStart(d Date) boolIsRangeStart reports whether d is the first date of the range.
func (CalendarResult) IsSelected
Section titled “func (CalendarResult) IsSelected”func (r CalendarResult) IsSelected(d Date) boolIsSelected reports whether d is selected: the selected date, or a date of the selected range (while a range is being chosen, of the range between its anchor and the focused date).
func (CalendarResult) IsToday
Section titled “func (CalendarResult) IsToday”func (r CalendarResult) IsToday(d Date) boolIsToday reports whether d is today (the Today option, else the current day).
func (CalendarResult) IsUnavailable
Section titled “func (CalendarResult) IsUnavailable”func (r CalendarResult) IsUnavailable(d Date) boolIsUnavailable reports whether d can not be selected: the caller’s IsDateUnavailable, or, while a range is being chosen, a date beyond the nearest unavailable one.
func (CalendarResult) Month
Section titled “func (CalendarResult) Month”func (r CalendarResult) Month(i int) DateMonth returns the first day of month i (0 to Months()-1) on display.
func (CalendarResult) Months
Section titled “func (CalendarResult) Months”func (r CalendarResult) Months() intMonths returns the number of months on display.
func (CalendarResult) NextDisabled
Section titled “func (CalendarResult) NextDisabled”func (r CalendarResult) NextDisabled() boolNextDisabled reports whether the next button is disabled.
func (CalendarResult) NextProps
Section titled “func (CalendarResult) NextProps”func (r CalendarResult) NextProps() tela.PropNextProps is CalendarResult.PrevProps for the next month, disabled at Max (aria-label “Next”).
func (CalendarResult) PrevDisabled
Section titled “func (CalendarResult) PrevDisabled”func (r CalendarResult) PrevDisabled() boolPrevDisabled reports whether the previous button is disabled.
func (CalendarResult) PrevProps
Section titled “func (CalendarResult) PrevProps”func (r CalendarResult) PrevProps() tela.PropPrevProps returns the props of the button that shows the previous month, or the previous VisibleMonths months (aria-label “Previous”): a click moves the months and the focused date back. It is disabled at Min. Give it to a Button.
func (CalendarResult) RowProps
Section titled “func (CalendarResult) RowProps”func (r CalendarResult) RowProps() tela.PropRowProps returns the props of a row of the grid: the weekday headers and each week.
func (CalendarResult) WeekdayNames
Section titled “func (CalendarResult) WeekdayNames”func (r CalendarResult) WeekdayNames(width i18n.NameWidth) []stringWeekdayNames returns the names of the seven weekdays in the locale at the given width (i18n.NameShort: “Mo”, i18n.NameNarrow: “M”), in the order of the columns: the first day of the week first.
func (CalendarResult) Weeks
Section titled “func (CalendarResult) Weeks”func (r CalendarResult) Weeks(i int) [][]DateWeeks returns the weeks of month i: rows of seven dates starting on the first day of the week, as many rows as the month needs (four to six), or six with FixedWeeks. The first and last rows hold dates of the neighbouring months, which CalendarResult.IsOutsideMonth reports.
type Collection
Section titled “type Collection”type Collection[K comparable] struct { // contains filtered or unexported fields}Collection is what the collection hooks share: the state of the items (selected, focused, disabled), the props of an item and the way to move the focus. It is embedded in the results of UseListBox, UseMenu and UseTabs.
func (Collection[K]) ActiveDescendant
Section titled “func (Collection[K]) ActiveDescendant”func (r Collection[K]) ActiveDescendant() tela.PropActiveDescendant returns the aria-activedescendant prop that points to the focused item, for the element that has the focus when the focus is virtual and external; it is empty while no item is focused.
func (Collection[K]) ClearFocus
Section titled “func (Collection[K]) ClearFocus”func (r Collection[K]) ClearFocus()ClearFocus forgets the focused key: no item is highlighted.
func (Collection[K]) Focus
Section titled “func (Collection[K]) Focus”func (r Collection[K]) Focus(strategy string)Focus focuses the first item (FocusFirst), the last (FocusLast), or the first selected one (the first when none is selected) for any other strategy, like the focus a Tab gives the collection.
func (Collection[K]) FocusKey
Section titled “func (Collection[K]) FocusKey”func (r Collection[K]) FocusKey(k K)FocusKey focuses the item with key k from a handler or an effect (a disabled or unknown key is ignored). With a roving focus the item’s element takes the focus, scrolling a virtual list to it first when it is not rendered; with a virtual focus it becomes the highlighted item.
func (Collection[K]) Focused
Section titled “func (Collection[K]) Focused”func (r Collection[K]) Focused() (K, bool)Focused returns the focused key, and whether there is one (it is remembered after the focus leaves the collection, since it is the item that takes the focus when it comes back).
func (Collection[K]) HandleKeyDown
Section titled “func (Collection[K]) HandleKeyDown”func (r Collection[K]) HandleKeyDown(e tela.KeyboardEvent)HandleKeyDown handles a key as the list’s own handler does (navigation, selection, typeahead, Enter and Space on the focused item). Props already carries it; this is for another element that has the focus while the list is shown, as a combobox’s input does with ExternalFocus.
Example
// A list whose focus stays on a text field, as in a combobox: the// field forwards its keys and carries aria-activedescendant.names := []string{"apple", "banana", "cherry"}page := func() tela.Node { lb := aria.UseListBox(aria.ListBoxOptions[string]{ Keys: names, SelectionMode: "single", AriaLabel: "Fruit", ExternalFocus: true, }) picked, _ := lb.Selected.First() return Div( Input(AriaLabel("Search"), OnKeyDown(lb.HandleKeyDown), lb.ActiveDescendant(), OnBlur(lb.ClearFocus)), Div(lb.Props, Range(len(names), func(i int) tela.Node { return Div(lb.ItemProps(names[i]), Key(names[i]), Class("h-8"), Text(names[i])) })), Textf("picked: %s", picked))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(300, 200))app.Focus(telatest.ByRole("textbox", "Search"))app.Press("ArrowDown")app.Press("ArrowDown")app.Press("Enter")app.ExpectText("picked: banana")fmt.Println("picked: banana")// Output: picked: bananafunc (Collection[K]) IsDisabled
Section titled “func (Collection[K]) IsDisabled”func (r Collection[K]) IsDisabled(k K) boolIsDisabled reports whether the item is in DisabledKeys.
func (Collection[K]) IsFocusVisible
Section titled “func (Collection[K]) IsFocusVisible”func (r Collection[K]) IsFocusVisible(k K) boolIsFocusVisible is IsFocused for a focus that came with the keyboard: the item that should show a focus ring.
func (Collection[K]) IsFocused
Section titled “func (Collection[K]) IsFocused”func (r Collection[K]) IsFocused(k K) boolIsFocused reports whether the item is the focused one while the focus is in the collection: with a roving focus, the element that has the focus; with a virtual focus, the highlighted item.
func (Collection[K]) IsSelected
Section titled “func (Collection[K]) IsSelected”func (r Collection[K]) IsSelected(k K) boolIsSelected reports whether the item is selected (with All, every item that is not disabled).
func (Collection[K]) ItemProps
Section titled “func (Collection[K]) ItemProps”func (r Collection[K]) ItemProps(k K) tela.PropItemProps returns the props of the item with key k: its role (option, menuitem… by the hook), the aria state (aria-selected, aria-checked, aria-disabled), the ref, a tab stop (0 on the focused item, -1 on the others, none on a disabled one) and the pointer and focus handlers. Give every item of Keys its props, and the element a key of its own. An item element holds one ref: do not attach another (use Collection.ItemRef).
func (Collection[K]) ItemRef
Section titled “func (Collection[K]) ItemRef”func (r Collection[K]) ItemRef(k K) tela.ElementRefItemRef returns the ref that ItemProps attaches to the item with key k, for measuring or focusing it; it has no element while the item is not rendered.
func (Collection[K]) Section
Section titled “func (Collection[K]) Section”func (r Collection[K]) Section(name string) SectionResultSection returns the props of a group of items and of its heading (React Aria’s useListBoxSection and useMenuSection). name identifies the section within the list; sections do not change the keys, which stay one flat list.
fruit := lb.Section("fruit")Div(fruit.Props, Div(fruit.HeadingProps, Class("text-xs text-muted-foreground"), Text("Fruit")), Div(lb.ItemProps("apple"), Key("apple"), Text("Apple")))type ColorAreaOptions
Section titled “type ColorAreaOptions”type ColorAreaOptions struct { // State is the color the area changes, from [UseColorState]. It is // required, and must come from the same component. State *ColorState // XChannel and YChannel are the channels along the two axes: any two // of [ColorHue], [ColorSaturation] and [ColorBrightness] (the HSB // model), the third one stays as it is. The defaults are the picker's: // saturation along X and brightness along Y. XChannel, YChannel ColorChannel // Direction is "ltr" (the default) or "rtl", which mirrors the X axis // and the left and right arrow keys, as React Aria's area does. Direction string // Disabled makes the thumb inert and disabled, and the area ignores // presses. Disabled bool // OnChangeEnd is called with the color when the user stops changing // it: the pointer is released, or a key changed it. OnChangeEnd func(tela.Color) // FormatValue returns the text of a channel's value for aria-valuetext // and [ColorAreaResult.ValueText]; the default is English ("Saturation // 50%"). Use package i18n for another language. FormatValue func(ColorChannel, float64) string // AriaLabel names the area ("Color area" by default); else it is named // by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the area. AriaLabelledBy tela.ElementRef}ColorAreaOptions configures UseColorArea: a two-dimensional slider that changes two channels of the color at once.
type ColorAreaResult
Section titled “type ColorAreaResult”type ColorAreaResult struct { // AreaProps go to the area element: the "group" role and its name, // and the handlers that make a press on it move the thumb to the // point and start dragging it, with a mouse, a finger or a pen. AreaProps tela.Prop // ThumbProps go to the thumb: the "slider" role (one for both // channels, as the semantics tree has no two-dimensional slider) with // the first channel's aria-valuenow and a value text that has both, a // tab stop, and the handlers: a press drags it (it keeps the grab, with // no jump), the arrow keys move it by one step (by a page with Shift, // mirrored in a "rtl" Direction), PageUp and PageDown change the Y // channel by a page, Home and End the X channel. ThumbProps tela.Prop // AreaRef and ThumbRef are the refs that the props attach. AreaRef, ThumbRef tela.ElementRef // Dragging is whether the thumb is being dragged, as of this build. Dragging bool // contains filtered or unexported fields}ColorAreaResult is what UseColorArea returns.
func UseColorArea
Section titled “func UseColorArea”func UseColorArea(o ColorAreaOptions) ColorAreaResultUseColorArea holds the interaction of a color area (React Aria’s useColorArea): a thumb in a rectangle, dragged with a pointer or moved with the keys, that changes two channels of the shared ColorState. The caller renders the area (its background is the gradients of the hue, as the kit’s does) and the thumb:
st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(tela.Hex("#6366f1"))})area := aria.UseColorArea(aria.ColorAreaOptions{State: st, AriaLabel: "Color"})size := tela.UseSize(area.AreaRef)return Div(area.AreaProps, Class("relative size-48 rounded-md"), Style(tela.BgFn(func() tela.Color { return aria.HSB{H: st.LiveHSB().H, S: 100, B: 100, A: 1}.Color() })), Div(area.ThumbProps, Class("absolute -ml-2.5 -mt-2.5 size-5 rounded-full border-2 border-white"), Style(tela.TranslateFn(func() (x, y float32) { px, py := area.ThumbPercent() return float32(px) / 100 * size.Get().Width, float32(py) / 100 * size.Get().Height }))))Dragging moves the thumb by repainting only: like UseSlider, an uncontrolled color builds when the drag starts and ends and when the pointer rests, not on every move.
Unlike a slider’s, the Home and End keys step the X channel by a page and PageUp and PageDown the Y channel, as React Aria’s area does. The third channel (the hue, in the default area) is the state’s: it is kept while the area moves the other two.
Example
t := &exampleT{}defer t.close()page := func() tela.Node { st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(aria.HSB{H: 220, S: 50, B: 50, A: 1}.Color())}) area := aria.UseColorArea(aria.ColorAreaOptions{State: st, AriaLabel: "Color"}) size := tela.UseSize(area.AreaRef) return Div(area.AreaProps, Class("relative size-48 rounded-md bg-muted"), Div(area.ThumbProps, Class("absolute left-0 top-0 -ml-2.5 -mt-2.5 size-5 rounded-full border-2 border-white"), Style(tela.TranslateFn(func() (x, y float32) { px, py := area.ThumbPercent() sz := size.Get() return float32(px) / 100 * sz.Width, float32(py) / 100 * sz.Height }))))}app := telatest.MountFunc(t, page)thumb := telatest.ByRole("slider", "Color")app.Focus(thumb)app.Press("Shift+ArrowRight") // a page of saturationapp.Press("ArrowDown")fmt.Println(app.AriaSnapshot())app.Expect(thumb, telatest.HasValue("Saturation 60%, Brightness 49%"))// Output:// - group "Color":// - slider "Color"func (ColorAreaResult) ThumbPercent
Section titled “func (ColorAreaResult) ThumbPercent”func (r ColorAreaResult) ThumbPercent() (x, y float64)ThumbPercent returns the position of the thumb in the area as it is now, in percent from the left edge (x) and from the top edge (y), 0 to 100: the X axis runs from the minimum of the X channel at the left to its maximum at the right (reversed in a “rtl” Direction) and the Y axis from the maximum at the top. It subscribes what reads it: read it in a paint-phase closure to place the thumb while it is dragged without building the component, as
Style(tela.TranslateFn(func() (x, y float32) { px, py := area.ThumbPercent() sz := size.Get() return float32(px) / 100 * sz.Width, float32(py) / 100 * sz.Height}))where size is tela.UseSize(area.AreaRef). Read in the component’s body it makes the component build on every move.
func (ColorAreaResult) ValueText
Section titled “func (ColorAreaResult) ValueText”func (r ColorAreaResult) ValueText() stringValueText returns the text of both channels as it is now (“Saturation 50%, Brightness 70%”).
type ColorChannel
Section titled “type ColorChannel”type ColorChannel stringColorChannel names one component of a color, for the sliders and the areas that change it.
const ( // ColorHue is the hue, in degrees from 0 to 360. ColorHue ColorChannel = "hue" // ColorSaturation is the saturation, from 0 to 100. ColorSaturation ColorChannel = "saturation" // ColorBrightness is the brightness of the HSB model, from 0 to 100. ColorBrightness ColorChannel = "brightness" // ColorLightness is the lightness of the HSL model, from 0 to 100. ColorLightness ColorChannel = "lightness" // ColorAlpha is the opacity, from 0 to 1. ColorAlpha ColorChannel = "alpha")The channels of the color hooks. Hue, saturation and brightness are those of HSB, saturation and lightness (in a “hsl” space) those of HSL, and alpha is the opacity.
type ColorFieldOptions
Section titled “type ColorFieldOptions”type ColorFieldOptions struct { // State is the color the field edits, from [UseColorState]. It is // required, and must come from the same component. State *ColorState // Alpha shows the opacity in the text ("#rrggbbaa"). Without it the // text is "#rrggbb" and a code of 3 or 6 digits keeps the opacity; // with it a code of 4 or 8 digits sets it too. Alpha bool // Disabled and ReadOnly stop the field from changing the color. Disabled, ReadOnly bool // AriaLabel names the field; else it is named by the element with // LabelProps, or by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the field, when it is not // the one with LabelProps. AriaLabelledBy tela.ElementRef // Ref is the ref to attach to the input, to focus it from outside; // the hook makes one when it is zero. Ref tela.ElementRef}ColorFieldOptions configures UseColorField: a text field for the hex code of the shared color.
type ColorFieldResult
Section titled “type ColorFieldResult”type ColorFieldResult struct { // InputProps go to the Input: the text, the handlers and the name. InputProps tela.Prop // LabelProps go to the element that labels the field; a press on it // focuses the field. LabelProps tela.Prop // InputValue is the text of the input as of this build: what the user // is typing, or the color's code. InputValue string // Editing is whether the text is a draft that is not committed yet. Editing bool // Ref is the ref that InputProps attaches to the input. Ref tela.ElementRef}ColorFieldResult is what UseColorField returns.
func UseColorField
Section titled “func UseColorField”func UseColorField(o ColorFieldOptions) ColorFieldResultUseColorField holds a hex text field for the shared color (React Aria’s useColorField). The user types a code (“#6366f1”, “6366f1” or “#fff”) and it is committed when the field loses the focus or on Enter: a code that parses sets the color, anything else (including an empty text) is reverted to the color’s code. While the field is not being edited it shows the color, so a drag elsewhere in the picker updates it. ArrowUp and ArrowDown add or subtract one from the color as a 24-bit number, PageUp and PageDown sixteen.
st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(tela.Hex("#6366f1"))})hex := aria.UseColorField(aria.ColorFieldOptions{State: st, AriaLabel: "Hex"})return Input(hex.InputProps, Class("w-28 rounded-md border px-2 py-1 font-mono"))Example
t := &exampleT{}defer t.close()page := func() tela.Node { st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(tela.Hex("#6366f1"))}) hex := aria.UseColorField(aria.ColorFieldOptions{State: st, AriaLabel: "Hex"}) return Div(Class("p-4"), Input(hex.InputProps, Class("w-28 rounded-md border px-2 font-mono")), Span(Text("color "+aria.FormatHexColor(st.Color(), false))))}app := telatest.MountFunc(t, page)field := telatest.ByRole("textbox", "Hex")app.Focus(field)app.Press("Control+a")app.Type("ff8800\n") // Enter commits; so does leaving the fieldapp.ExpectText("color #ff8800")app.Press("Control+a")app.Type("oops\n") // not a color: revertedfmt.Println(app.AriaSnapshot())// Output:// - textbox "Hex": "#ff8800"// - text: "color #ff8800"type ColorSliderOptions
Section titled “type ColorSliderOptions”type ColorSliderOptions struct { // State is the color the slider changes, from [UseColorState]. It is // required, and must come from the same component. State *ColorState // Channel is the channel the slider changes. It is required: // [ColorHue] (0 to 360), [ColorAlpha] (0 to 1), [ColorSaturation] and // [ColorBrightness] (0 to 100), or [ColorLightness] with a "hsl" // Space. Channel ColorChannel // Space is the color model of the saturation and the lightness: // "hsb" (the default; it has a brightness) or "hsl" (it has a // lightness, and its saturation is not the HSB's). Hue and alpha are // the same in both. Space string // Orientation is "horizontal" (the default) or "vertical" (the minimum // at the bottom). Orientation string // Direction is "ltr" (the default) or "rtl", which mirrors a // horizontal slider and its arrow keys. Direction string // Disabled makes the thumb inert and disabled. Disabled bool // OnChangeEnd is called with the color when the user stops changing it. OnChangeEnd func(tela.Color) // FormatValue returns the text of the value for aria-valuetext and // [ColorSliderResult.ValueText]; the default is English ("Hue 180°", // "Alpha 80%"). Use package i18n for another language. FormatValue func(ColorChannel, float64) string // AriaLabel names the slider; the default is the channel's name // ("Hue"). A visible label is the slider's name with AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the slider. AriaLabelledBy tela.ElementRef}ColorSliderOptions configures UseColorSlider: a slider for one channel of the shared color.
type ColorSliderResult
Section titled “type ColorSliderResult”type ColorSliderResult struct { // Props go to the slider's root element: the "group" role and its // name. Props tela.Prop // TrackProps go to the track, which is the gradient: a press on it // moves the thumb to the point and starts dragging it. TrackProps tela.Prop // ThumbProps go to the thumb: the "slider" role, aria-valuenow, // aria-valuetext (the channel's name and value), a tab stop and the // handlers of [UseSlider] (arrows by one step, Shift and the page keys // by a page, Home and End to the ends). ThumbProps tela.Prop // TrackRef and ThumbRef are the refs that the props attach. TrackRef, ThumbRef tela.ElementRef // Channel is the channel of the slider, and Min, Max and Step its // range: 0 to 360 for the hue, 0 to 1 by 0.01 for the alpha and 0 to // 100 for the others. Channel ColorChannel Min, Max, Step float64 // Dragging is whether the thumb is being dragged, as of this build. Dragging bool // contains filtered or unexported fields}ColorSliderResult is what UseColorSlider returns.
func UseColorSlider
Section titled “func UseColorSlider”func UseColorSlider(o ColorSliderOptions) ColorSliderResultUseColorSlider holds the interaction of a slider for one channel of a color (React Aria’s useColorSlider). It is built on UseSlider, so it drags, rounds and answers keys like it, with the channel’s range, step and page. The caller renders the track (a gradient of TrackColors) and the thumb:
st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(tela.Hex("#6366f1"))})hue := aria.UseColorSlider(aria.ColorSliderOptions{State: st, Channel: aria.ColorHue})track := tela.UseSize(hue.TrackRef)return Div(hue.Props, Class("w-64"), Div(hue.TrackProps, Class("relative h-3 rounded-full"), Div(hue.ThumbProps, Class("absolute -top-1 -ml-2.5 size-5 rounded-full border-2 border-white"), Style(tela.TranslateFn(func() (x, y float32) { return float32(hue.ThumbPercent()) / 100 * track.Get().Width, 0 })))))Dragging repaints and does not build, as the slider does.
Example
t := &exampleT{}defer t.close()page := func() tela.Node { st := aria.UseColorState(aria.ColorStateOptions{DefaultValue: new(aria.HSB{H: 220, S: 80, B: 90, A: 1}.Color())}) hue := aria.UseColorSlider(aria.ColorSliderOptions{State: st, Channel: aria.ColorHue}) track := tela.UseSize(hue.TrackRef) return Div(hue.Props, Class("w-64 p-4"), Div(hue.TrackProps, Class("relative h-3 w-full rounded-full bg-muted"), Div(hue.ThumbProps, Class("absolute -top-1 -ml-2.5 size-5 rounded-full border-2 border-white"), Style(tela.TranslateFn(func() (x, y float32) { return float32(hue.ThumbPercent()) / 100 * track.Get().Width, 0 })))))}app := telatest.MountFunc(t, page)thumb := telatest.ByRole("slider", "Hue")app.Focus(thumb)app.Press("PageUp") // a page of hue is 15 degreesfmt.Println(app.AriaSnapshot())app.Expect(thumb, telatest.HasValue("Hue 235°"))// Output:// - group "Hue":// - slider "Hue"func (ColorSliderResult) ThumbPercent
Section titled “func (ColorSliderResult) ThumbPercent”func (r ColorSliderResult) ThumbPercent() float64ThumbPercent returns the position of the thumb along the track as it is now, from 0 (Min) to 100 (Max), exactly as SliderResult.ThumbPercent: it subscribes what reads it, so read it in a paint-phase closure to place the thumb while it is dragged without building the component.
func (ColorSliderResult) TrackColors
Section titled “func (ColorSliderResult) TrackColors”func (r ColorSliderResult) TrackColors() []tela.ColorTrackColors returns the colors of the track from the minimum of the channel to its maximum, with the other channels of the color as they are now: the stops of the gradient behind the thumb. The hue has seven (every 60 degrees), the lightness three (black, the color, white) and the other channels two. For the alpha the first is transparent and the last opaque, to show over a checkerboard. It subscribes what reads it, so a track whose gradient follows the color is drawn from a paint-phase closure.
func (ColorSliderResult) Value
Section titled “func (ColorSliderResult) Value”func (r ColorSliderResult) Value() float64Value returns the value of the channel as it is now, in the channel’s range. Like ThumbPercent it subscribes what reads it.
func (ColorSliderResult) ValueText
Section titled “func (ColorSliderResult) ValueText”func (r ColorSliderResult) ValueText() stringValueText returns the text of the value as it is now.
type ColorState
Section titled “type ColorState”type ColorState struct { // contains filtered or unexported fields}ColorState is the color that the area, the sliders and the field of one picker share (react-stately’s useColorPickerState), made by UseColorState. It keeps the color as an HSB and not as the tela.Color it reports, so that dragging through a gray or through black does not lose the hue (or the saturation), and a color the caller passes back is recognized as the one the state reported.
The hooks that use a ColorState must be called in the component that made it: the state asks that component to build when a drag starts, when it ends and when the pointer has rested, as the slider does, and not on every move.
func UseColorState
Section titled “func UseColorState”func UseColorState(o ColorStateOptions) *ColorStateUseColorState holds the color of a picker and returns the state that UseColorArea, UseColorSlider and UseColorField share.
func (*ColorState) Color
Section titled “func (*ColorState) Color”func (s *ColorState) Color() tela.ColorColor returns the color as of the last build. While a drag moves an uncontrolled state the build waits for the pointer to rest or the drag to end; read ColorState.LiveColor in a paint-phase closure for the color that is shown now.
func (*ColorState) HSB
Section titled “func (*ColorState) HSB”func (s *ColorState) HSB() HSBHSB returns the color as of the last build, in the HSB model with the hue it keeps.
func (*ColorState) LiveColor
Section titled “func (*ColorState) LiveColor”func (s *ColorState) LiveColor() tela.ColorLiveColor returns the color as it is now. It subscribes what reads it: read it in a paint-phase closure (tela.BgFn, tela.TextColorFn) to follow a drag without building; read in the component’s body it makes the component build on every move.
func (*ColorState) LiveHSB
Section titled “func (*ColorState) LiveHSB”func (s *ColorState) LiveHSB() HSBLiveHSB is ColorState.LiveColor in the HSB model.
func (*ColorState) Set
Section titled “func (*ColorState) Set”func (s *ColorState) Set(c tela.Color)Set asks for a color, as a swatch or a hex field does. A color that is the one the state holds changes nothing; a gray keeps the hue.
func (*ColorState) SetHSB
Section titled “func (*ColorState) SetHSB”func (s *ColorState) SetHSB(h HSB)SetHSB asks for a color in the HSB model. The values are clamped to their ranges.
type ColorStateOptions
Section titled “type ColorStateOptions”type ColorStateOptions struct { // Value points to the color of a controlled state; nil leaves it to // the hook. Value *tela.Color // DefaultValue is the color of an uncontrolled state at the start; nil // starts at opaque black (tela.Black). It is a pointer because the // zero Color is a color too, transparent black: new(tela.Hex("#6366f1")). DefaultValue *tela.Color // OnChange is called with the next color every time the user changes // it: while dragging, on each key, on a hex commit and when a swatch is // set. The color is in the sRGB gamut. OnChange func(tela.Color)}ColorStateOptions configures UseColorState.
The color is uncontrolled when Value is nil (the state keeps it, starting at DefaultValue) and controlled when Value points to the caller’s state: the hooks show it and only ask OnChange for the next one, so a change the caller does not store is refused. OnChange is also called for an uncontrolled state.
// A picker that keeps its color.aria.ColorStateOptions{DefaultValue: new(tela.Hex("#6366f1"))}// A controlled one.c, setC := tela.UseState(tela.Hex("#6366f1"))aria.ColorStateOptions{Value: &c, OnChange: setC.Set}type ComboboxOptions
Section titled “type ComboboxOptions”type ComboboxOptions[K comparable] struct { // Keys are the items to show, filtered by the caller: the caller keeps // the text, filters its data as it likes (a server search, a fuzzy // match) and gives the result on every build. Or give Items. Keys []K // Items are all the items, in order, and the hook shows the ones that // match the text with Filter (React Aria's defaultItems with // defaultFilter). Give Keys or Items, not both; an empty, non-nil Items // is a combobox without items. ComboboxResult.Keys are the items to // render. Items []K // Filter decides whether the text of an item matches what is typed; // [Contains] (the default), [StartsWith] or your own function. It // applies to Items only. Filter Filter // TextValue is the text of an item: it goes to the input when the item // is chosen and Filter matches it. The default is fmt.Sprint of the // key. TextValue func(K) string DisabledKeys []K Selected Selection[K] DefaultSelected Selection[K] OnSelectionChange func(Selection[K]) // InputValue points to the text of a controlled combobox, with // OnInputChange asking for the next one; nil leaves it to the hook, // which starts at DefaultInputValue (else the text of the selected // item). OnInputChange is also called for an uncontrolled combobox and // for the text that choosing an item or leaving the input puts back. InputValue *string DefaultInputValue string OnInputChange func(string) // Open points to the open state of a controlled list; nil leaves it to // the hook, starting at DefaultOpen. OnOpenChange is called with the // next state. Open *bool DefaultOpen bool OnOpenChange func(open bool) // MenuTrigger is how the list opens: [MenuTriggerInput] (the default), // [MenuTriggerFocus] or [MenuTriggerManual]. ArrowDown (first item) // and ArrowUp (last) and the toggle button open it in every mode, with // all the items. MenuTrigger string // AllowsCustomValue lets the text stay as typed when the input loses // the focus or Enter is pressed with no item chosen, clearing the // selection. Without it the text goes back to that of the selected item // (empty when none is). AllowsCustomValue bool // AllowsEmptyCollection keeps the list open when no item matches, to // show a "no results" message. By default it closes. AllowsEmptyCollection bool // Disabled makes the input and the button inert and disabled; ReadOnly // makes the input read-only and the list does not open. Disabled, ReadOnly bool // ShouldFocusWrap makes ArrowDown on the last item highlight the first, // and ArrowUp on the first the last. ShouldFocusWrap bool // Placement is where the popover goes around its anchor, one of // Floating UI's 12 placements; "bottom-start" by default. Offset is the // distance in pixels, and Floating are more options of [UseFloating]. Placement string Offset float32 Floating []FloatingOption // MatchWidth makes the popover as wide as the anchor. MatchWidth bool // AnchorRef is the element the popover is placed next to and // MatchWidth measures: the wrapper of the input and the button, say. The // zero value anchors it to the input. AnchorRef tela.ElementRef // AriaLabel names the input when there is no label element; with // LabelProps the label element names it. AriaLabel string // ButtonLabel is the accessible name of the toggle button that // ButtonProps go to (default "Show suggestions"). Set it to translate // the name. ButtonLabel string // Ref is the input's ref, for a hook used together with others on the // same element; the zero value makes the hook create its own. Ref tela.ElementRef}ComboboxOptions configures UseCombobox. The items are keyed like those of a list box: see ListBoxOptions for TextValue, DisabledKeys, and the controlled and uncontrolled Selected, DefaultSelected and OnSelectionChange (a combobox holds at most one selected key).
type ComboboxResult
Section titled “type ComboboxResult”type ComboboxResult[K comparable] struct { // InputProps go to the Input: the "combobox" role, the text and its // change, the keys (arrows, Enter, Tab, Escape), aria-autocomplete, // aria-expanded, aria-controls while open and aria-activedescendant // for the highlighted item. The input keeps the focus: the items are // highlighted, never focused. InputProps tela.Prop // ButtonProps go to an optional toggle button: it shows or hides the // list with every item, and keeps the focus on the input. It is not a // tab stop and is labelled with ButtonLabel ("Show suggestions" by // default; an AriaLabel after the props also changes it). ButtonProps tela.Prop // LabelProps go to the label element: it names the input, and a click // on it focuses the input. LabelProps tela.Prop // PopoverProps go to the popover's element, which holds the list box: // it is fixed to the window and placed next to the anchor in the frame // that shows it, it is on the overlay stack (Escape closes it), and a // press in it keeps the focus on the input. With MatchWidth it is as // wide as the anchor. Render it in a Portal, only while Open. PopoverProps tela.Prop // ArrowProps go to an arrow element inside the popover. ArrowProps tela.Prop // ListBox is the list: its Props go to the element that holds the // items and ItemProps(k) to each item, as in [UseListBox]. The items // are rendered inside the popover, only while Open, from Keys. ListBox ListBoxResult[K] // Keys are the items to render: ComboboxOptions.Keys, or the Items that // match the text (all of them after the list was opened with the // arrows or the button, until the text changes). Keys []K // InputValue is the text of the input. InputValue string // ShowAll is true after the list was opened by hand (the button, the // arrows, or the focus with MenuTriggerFocus) and until the text // changes: the list shows every item whatever the text. A hook given // Items does it itself; a caller who filters Keys should too. ShowAll bool // Selected is the current selection: [Selection.First] is the selected // key. Selected Selection[K] // Open is whether the list is open; SetOpen asks for a state, Toggle for // the other one and Close for closed. Open bool SetOpen func(open bool) Toggle func() Close func() // Pressed is true while the button is pressed. Pressed bool // Placement is the popover's placement after flipping (see // [PopoverResult]). Placement string // InputRef, ButtonRef and PopoverRef are the refs of those elements. InputRef, ButtonRef, PopoverRef tela.ElementRef}ComboboxResult is what UseCombobox returns.
func UseCombobox
Section titled “func UseCombobox”func UseCombobox[K comparable](o ComboboxOptions[K]) ComboboxResult[K]UseCombobox holds the state of a combobox and returns the props of its input, its toggle button, its popover and its list (React Aria’s useComboBox with useComboBoxState): a text field that filters a list of options, and a list that opens next to it.
cb := aria.UseCombobox(aria.ComboboxOptions[string]{ Items: fruit, TextValue: capital, AriaLabel: "Fruit", MatchWidth: true,})return Fragment( Div(Class("flex"), Input(cb.InputProps, Class("w-48 rounded-l-md border px-3 py-2")), Button(cb.ButtonProps, Class("rounded-r-md border px-2"), Icon(chevronDown))), Portal(If(cb.Open, Div(cb.PopoverProps, Class("rounded-md border bg-popover p-1 shadow-md"), Div(cb.ListBox.Props, Class("max-h-60 overflow-auto"), Range(len(cb.Keys), func(i int) tela.Node { k := cb.Keys[i] return Div(cb.ListBox.ItemProps(k), Key(k), Class("rounded px-2 py-1.5 aria-selected:font-medium"), If(cb.ListBox.IsFocused(k), Class("bg-accent")), Text(capital(k))) }))))))Typing filters the list and opens it (see MenuTrigger). The input keeps the focus: ArrowDown and ArrowUp highlight the next and previous item (the first and the last, opening the list when closed), Enter chooses the highlighted item, which sets the text to its TextValue and closes the list, Tab chooses it and moves on, and Escape closes the list and puts the text back to that of the selected item (cleared when there is none). When the input loses the focus the text is committed: reset to the selected item’s, or kept as a custom value with AllowsCustomValue. Home, End, Space and the left and right arrows stay with the text field, and nothing moves while an IME composes text.
Items are highlighted with Collection.IsFocused (there is no DOM focus on them); the input carries aria-activedescendant. In the aria snapshot the input is combobox "Fruit" [expanded]: b, the button button "Show suggestions" and the list a listbox named by the label.
Example
t := &exampleT{}defer t.close()page := func() tela.Node { cb := aria.UseCombobox(aria.ComboboxOptions[string]{ Items: fruits, TextValue: capital, AriaLabel: "Fruit", MatchWidth: true, }) return Div(Class("p-4"), Div(Class("flex"), Input(cb.InputProps, Class("w-48 rounded-l-md border px-3 py-2")), Button(cb.ButtonProps, Class("rounded-r-md border px-2"), Text("v"))), Portal(If(cb.Open, Div(cb.PopoverProps, Class("rounded-md border bg-popover p-1 shadow-md"), Div(cb.ListBox.Props, Class("max-h-60 overflow-auto"), Range(len(cb.Keys), func(i int) tela.Node { k := cb.Keys[i] return Div(cb.ListBox.ItemProps(k), Key(k), Class("rounded px-2 py-1.5"), If(cb.ListBox.IsFocused(k), Class("bg-accent")), Text(capital(k))) }))))))}app := telatest.MountFunc(t, page, telatest.Size(400, 300))app.Focus(telatest.ByRole("combobox", "Fruit"))app.Type("blu") // filters and opens the listfmt.Println(app.AriaSnapshot())app.Press("ArrowDown") // highlights the item, the input keeps the focusapp.Press("Enter") // chooses itfmt.Println(app.AriaSnapshot())// Output:// - combobox "Fruit" [expanded]: blu// - button "Show suggestions" [expanded]: v// - listbox "Fruit":// - option "Blueberry"// - combobox "Fruit": Blueberry// - button "Show suggestions": vExample (allowsCustomValue)
t := &exampleT{}defer t.close()page := func() tela.Node { cb := aria.UseCombobox(aria.ComboboxOptions[string]{ Items: fruits, TextValue: capital, AriaLabel: "Tag", AllowsCustomValue: true, OnSelectionChange: func(s aria.Selection[string]) { fmt.Println("selected:", s) }, }) return Input(cb.InputProps, Class("w-48 border"))}app := telatest.MountFunc(t, page, telatest.Size(300, 100))app.Focus(telatest.ByRole("combobox", "Tag"))app.Fill(telatest.ByRole("combobox", "Tag"), "kiwi")app.Press("Tab") // the text stays, and the selection is clearedfmt.Println(app.AriaSnapshot())// Output: - combobox "Tag": kiwitype ContextMenuTriggerOptions
Section titled “type ContextMenuTriggerOptions”type ContextMenuTriggerOptions struct { // Open points to the open state of a controlled menu; nil leaves the // state to the hook, which starts closed. OnOpenChange is called with // the next state: for a controlled menu it is how the change is asked // for, for an uncontrolled one a notification. A menu opens at a point, // so there is no DefaultOpen. Open *bool OnOpenChange func(open bool) // Disabled makes the area ignore the gestures that open the menu: the // browser's (here, the platform's) own, and an outer context menu's, // take over. Disabled bool // NoLongPress keeps a touch or a pen that stays down from opening the // menu. The long press is recognized for touch contacts and pens only // (a mouse button held on the area, or on a child, clicks as usual), // and it wins over the click of that touch: turn it off for an area // whose content is held down with a finger for something else. NoLongPress bool // Ref is the area's ref and MenuRef the menu's, for hooks used together // with others on the same element; the zero value makes the hook // create its own. Ref, MenuRef tela.ElementRef}ContextMenuTriggerOptions configures UseContextMenuTrigger.
type ContextMenuTriggerResult
Section titled “type ContextMenuTriggerResult”type ContextMenuTriggerResult struct { // TriggerProps go to the area that has the context menu: the ref and // the gestures that open the menu (see [UseContextMenuTrigger]). TriggerProps tela.Prop // MenuProps go to the menu's element: the ref and a focus scope that // gives the focus back when the menu closes. The menu's other hooks and // [UsePopover] share MenuRef. The menu has no name of its own: give // [MenuOptions].AriaLabel one. MenuProps tela.Prop // Open is whether the menu is open. Open bool // FocusStrategy is where the focus goes when the menu opens: // [FocusFirst] for a key or a long press, "" for the menu itself // (opened with a mouse); a move of the open menu keeps it. Pass it to // [MenuOptions].FocusStrategy. FocusStrategy string // Anchor returns the virtual rectangle the menu is placed at, in window // coordinates: a point where the pointer was or the area's top-left // corner. It is [AnchorRect]'s argument, for a menu placed with // [UseFloating] instead of [UsePopover]. Each build's Anchor belongs to // the opening that build saw: it follows the menu while it is moved // (a long press or OpenAt while it is open) and keeps its point once // the menu closes, so that a menu exiting in a Presence stays where it // was when the next one opens elsewhere. Anchor func() tela.Rect // OpenAt opens the menu at a point of the window (a click handler of // your own, a "More" button). Close closes it, and a menu closes itself // when an item is chosen by calling Close. SetOpen asks for a state. OpenAt func(x, y float32) SetOpen func(open bool) Close func() // Ref is the area's ref and MenuRef the menu's. Ref, MenuRef tela.ElementRef}ContextMenuTriggerResult is what UseContextMenuTrigger returns.
func UseContextMenuTrigger
Section titled “func UseContextMenuTrigger”func UseContextMenuTrigger(o ContextMenuTriggerOptions) ContextMenuTriggerResultUseContextMenuTrigger holds the open state of a context menu and returns the props of its area, which opens the menu at the pointer (the contextmenu event of the platform, Radix UI’s ContextMenu):
- a right click, at the pointer (the focus stays where it was, and the menu itself takes it),
- a touch or a pen that stays down for 500 ms, at the finger, with the first item focused (Radix’s rule; a mouse button held down is not a long press here, so it clicks what it is on as usual),
- Shift+F10 and the ContextMenu key, with the first item focused, at the top-left corner of the area.
The event’s default is prevented and its propagation stopped, so the innermost area with a context menu wins. The menu is rendered by the caller, in a Portal, and UsePopover places it at the point with AnchorRect, flipped and shifted to stay in the window; Escape, a press outside it with any mouse button (Radix’s DismissableLayer), the focus leaving and choosing an item close it, and the focus goes back to where it was. A right click elsewhere on the area while the menu is open closes it and opens it again at the new point, as a new menu: it takes the focus as when it opens (the right click’s contextmenu event comes after the release that closes the menu). A touch or a pen that stays down elsewhere on the area while the menu is open moves the open menu there, and its release does not close it (Radix); a quick tap there closes it, as any press outside does. A right click or a long press in another area with a context menu closes this one and opens that one, and a right click outside every area just closes it. A menu rendered in a Presence (to fade out) comes back as the same element when it opens again while it exits, at the new point and without taking the focus: give it a new key each time it opens (count the opens in OnOpenChange), as kit’s ContextMenu does, and the exiting one fades out at its own point while a new one opens and takes the focus.
cm := aria.UseContextMenuTrigger(aria.ContextMenuTriggerOptions{})return Fragment( Div(cm.TriggerProps, Class("h-48 w-full rounded-md border"), Text("Right click here")), Portal(If(cm.Open, fileMenu{cm})))
type fileMenu struct{ cm aria.ContextMenuTriggerResult }
func (m fileMenu) Build() tela.Node { pop := aria.UsePopover(m.cm.PopoverOptions()) menu := aria.UseMenu(aria.MenuOptions[string]{ Keys: actions, OnAction: run, OnClose: m.cm.Close, AriaLabel: "Actions", AutoFocus: true, FocusStrategy: m.cm.FocusStrategy, Ref: m.cm.MenuRef, }) return Div(pop.Props, m.cm.MenuProps, menu.Props, Class("w-48 rounded-md border bg-popover p-1"), ...)}The menu is the one of a dropdown menu: UseMenu takes the same results. The area is not focusable by itself; the keys reach it from the element inside it that has the focus, and a keyboard menu opens at the area’s corner, not at that element.
Example
// A context menu: Shift+F10 on the focused button opens it, Enter// chooses the first item and the focus goes back to the button.var ev eventsapp := telatest.MountFunc(&exampleTB{}, ctxPage(aria.ContextMenuTriggerOptions{}, &ev), telatest.Size(400, 300))app.Focus(telatest.ByTestID("inner"))app.Press("Shift+F10")fmt.Println(app.AriaSnapshot())app.Press("Enter")app.ExpectFocused(telatest.ByTestID("inner"))fmt.Println(ev.take())// Output:// - button "Inner"// - button "Outside"// - menu "Actions":// - menuitem "Copy"// - menuitem "Paste"// - menuitem "Delete"// action:copyfunc (ContextMenuTriggerResult) PopoverOptions
Section titled “func (ContextMenuTriggerResult) PopoverOptions”func (r ContextMenuTriggerResult) PopoverOptions() PopoverOptionsPopoverOptions returns the options of UsePopover for the menu: anchored at the point the menu was opened with (“bottom-start”, flipped and shifted to stay in the window), through ContextMenuTriggerResult.Anchor, so a menu exiting in a Presence stays at its own point. The popover is not modal, so that a right click on the area while the menu is open reaches it: the press closes the menu, as any press outside it does (a right click too), and the right click opens it again at the new point. Change what you need in the value returned (Offset; append to Floating, whose first option is the anchor).
type Date
Section titled “type Date”type Date struct { Year int Month time.Month Day int}Date is a day of the Gregorian calendar, without a time of day or a time zone: what a calendar selects (“the 5th of October 2026”), the same wherever it is read. The zero Date means “no date”.
It is a comparable value, unlike a time.Time (whose == also compares the zone pointer and the monotonic clock), so it can be a map key, the state of tela.UseState, a prop that skips a build when it is equal, and it has no daylight saving time to trip date arithmetic on: AddDays(1) is always the next day. Convert with DateOf and Date.Time.
d := aria.Date{Year: 2026, Month: time.October, Day: 5}d.AddMonths(1) // 2026-11-05d.AddDays(-5) // 2026-09-30aria.Date{2026, 1, 31}.AddMonths(1) // 2026-02-28: the day is clampedExample
oct := aria.Date{Year: 2026, Month: time.October, Day: 31}fmt.Println(oct.AddMonths(1), oct.AddDays(1), oct.Weekday())fmt.Println(aria.DateOf(time.Date(2026, 1, 31, 23, 0, 0, 0, time.UTC)).AddMonths(1))// Output:// 2026-11-30 2026-11-01 Saturday// 2026-02-28func DateOf
Section titled “func DateOf”func DateOf(t time.Time) DateDateOf returns the calendar day of t in t’s own time zone.
func Today
Section titled “func Today”func Today() DateToday returns the current day in the local time zone. Calendars take their notion of today from an option (CalendarOptions.Today), so tests can fix it; this is the default.
func (Date) AddDays
Section titled “func (Date) AddDays”func (d Date) AddDays(n int) DateAddDays returns the day n days after d (before it when n is negative).
func (Date) AddMonths
Section titled “func (Date) AddMonths”func (d Date) AddMonths(n int) DateAddMonths returns the same day of the month n months after d; when that month is shorter the day is the last one of it (January 31 plus one month is February 28 or 29).
func (Date) AddYears
Section titled “func (Date) AddYears”func (d Date) AddYears(n int) DateAddYears returns the same day n years after d, with the day clamped like Date.AddMonths (February 29 plus one year is February 28).
func (Date) After
Section titled “func (Date) After”func (d Date) After(o Date) boolAfter reports whether d is a day after o.
func (Date) Before
Section titled “func (Date) Before”func (d Date) Before(o Date) boolBefore reports whether d is a day before o.
func (Date) Compare
Section titled “func (Date) Compare”func (d Date) Compare(o Date) intCompare returns -1, 0 or 1 when d is before, the same as or after o.
func (Date) IsValid
Section titled “func (Date) IsValid”func (d Date) IsValid() boolIsValid reports whether d is a day that exists: January 1 of year 1 to December 31 of year 9999, with the right number of days in its month.
func (Date) IsZero
Section titled “func (Date) IsZero”func (d Date) IsZero() boolIsZero reports whether d is the zero Date, no date at all.
func (Date) String
Section titled “func (Date) String”func (d Date) String() stringString returns d as ISO 8601, “2026-10-05” (and “none” for the zero Date). Unlike a localized date it is unique per day, so it is the key of a calendar cell: Key(day.String()).
func (Date) Time
Section titled “func (Date) Time”func (d Date) Time(loc *time.Location) time.TimeTime returns midnight of d in loc (UTC when loc is nil).
func (Date) Weekday
Section titled “func (Date) Weekday”func (d Date) Weekday() time.WeekdayWeekday returns the day of the week of d.
type DateRange
Section titled “type DateRange”type DateRange struct { Start, End Date}DateRange is a span of days, both ends included: the value of a range calendar. It is the zero value when nothing is selected; Start is never after End in a range a calendar makes.
func (DateRange) Contains
Section titled “func (DateRange) Contains”func (r DateRange) Contains(d Date) boolContains reports whether d is in r, its ends included.
func (DateRange) IsZero
Section titled “func (DateRange) IsZero”func (r DateRange) IsZero() boolIsZero reports whether r is the empty selection.
type DialogOptions
Section titled “type DialogOptions”type DialogOptions struct { // Role is "dialog" (the default) or "alertdialog", for a dialog that // interrupts the user with a message that needs a response. Another // value panics in dev mode. Role string // Modal marks the dialog aria-modal and keeps Tab inside it. Make the // dialog's [UseOverlay] modal too, so that the rest of the window is // inert. Modal bool // AriaLabel names the dialog when it has no visible title. Without it // the dialog is named by the element that has TitleProps. AriaLabel string // Ref is the dialog element's ref, for a hook used together with others // on the same element (the overlay trigger's OverlayRef, UsePopover); // the zero value makes the hook create its own. Ref tela.ElementRef}DialogOptions configures UseDialog.
type DialogResult
Section titled “type DialogResult”type DialogResult struct { // Props go to the dialog's element: its role, aria-modal, its name, a // focus scope that moves the focus into the dialog when it opens, keeps // it there (with Modal) and gives it back to the element that had it // when the dialog closes, and the ref. Props tela.Prop // TitleProps go to the element that titles the dialog (a heading): its // text names the dialog. TitleProps tela.Prop // Manager moves the focus inside the dialog. Manager FocusManager // Ref is the ref attached to the dialog's element by Props. Ref tela.ElementRef}DialogResult is what UseDialog returns.
func UseDialog
Section titled “func UseDialog”func UseDialog(o DialogOptions) DialogResultUseDialog returns the props of a dialog (React Aria’s useDialog): its role, its name from the title, and the focus: it goes to the first tabbable element inside when the dialog opens (the dialog itself when there is none), and returns to the element that had it when the dialog closes. Combine it with UseOverlay for the dismissal and the modality, on the same element.
dlg := aria.UseDialog(aria.DialogOptions{Modal: true})ov := aria.UseOverlay(aria.OverlayOptions{Open: open, OnClose: close, Dismissable: true, Modal: true})return Portal(If(open, Div(ov.Props, dlg.Props, Class("w-96 rounded-xl bg-background p-6 shadow-xl"), H2(dlg.TitleProps, Class("text-lg font-semibold"), Text("Delete project?")), Button(OnClick(close), Text("Cancel")))))An “alertdialog” is not dismissed by a press outside it: leave Dismissable off. The dialog element takes the focus when it has nothing tabbable (it is focusable with a script, not in the Tab order), and, in a modal dialog, when the focused element inside it unmounts (Radix’s FocusScope), so the keys still reach the dialog and Tab goes on inside.
Example
// A modal dialog named by its title: it takes the focus when it opens,// keeps Tab inside, and gives the focus back when it closes.page := func() tela.Node { trig := aria.UseOverlayTrigger(aria.OverlayTriggerOptions{}) ov := aria.UseOverlay(aria.OverlayOptions{Open: trig.Open, OnClose: trig.Close, Dismissable: true, Modal: true}) dlg := aria.UseDialog(aria.DialogOptions{Modal: true, Ref: trig.OverlayRef}) return Div( Button(trig.TriggerProps, Text("Delete")), Portal(If(trig.Open, Div(ov.Props, dlg.Props, Class("w-64 bg-white p-4"), H2(dlg.TitleProps, Text("Delete project?")), Button(Text("Cancel")), Button(OnClick(trig.Close), Text("Delete"))))))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(300, 200))app.Click(telatest.ByRole("button", "Delete"))fmt.Println(app.AriaSnapshot())app.Press("Escape")app.ExpectFocused(telatest.ByRole("button", "Delete"))// Output:// - dialog "Delete project?":// - heading "Delete project?" [level=2]// - button "Cancel"// - button "Delete"type Filter
Section titled “type Filter”type Filter func(text, query string) boolFilter decides whether the text of an item matches what the user typed: the filter of ComboboxOptions. Contains, StartsWith and EndsWith are ready to use.
type FlipOptions
Section titled “type FlipOptions”type FlipOptions struct { // Padding is the space to keep between the element and the edges of the // area it must stay in (the window, or the visible part of the scroll // containers around it when it is not in a Portal). Padding float32 // NoMainAxis stops flip from checking the overflow on the side of the // placement (Floating UI's mainAxis: false); NoCrossAxis on the // alignment axis (crossAxis: false). NoMainAxis, NoCrossAxis bool // CrossAxisAlignment is Floating UI's crossAxis: 'alignment': the // overflow on the alignment axis only counts while the next placement // is on the same axis as the initial one, so the element changes side // only when every placement on its side overflows. CrossAxisAlignment bool // FallbackPlacements are the placements to try, in order, after the // initial one. Nil tries the opposite one (and, for an aligned // placement, its other alignments first). FallbackPlacements []string // FallbackStrategy is what to do when no placement fits: "bestFit" (the // default) takes the one that overflows least, "initialPlacement" goes // back to the initial one. FallbackStrategy string // FallbackAxisSideDirection adds the placements on the other axis to // the fallbacks when FallbackPlacements is nil: "none" (the default), // "start" or "end", the side tried first. FallbackAxisSideDirection string // NoFlipAlignment stops an aligned placement from trying its other // alignment (Floating UI's flipAlignment: false). NoFlipAlignment bool}FlipOptions are the options of Flip; the zero value is Floating UI’s defaults.
type Floating
Section titled “type Floating”type Floating struct { // Props go on the floating element: they make it fixed to the window at // its top-left corner (a later class such as "absolute" in the element's // Class wins) and place it next to its anchor. Put the element in a // Portal so that no ancestor clips it. Props tela.Prop // ArrowProps go on the arrow element (the one whose ElementRef was given // to [Arrow]): they make it absolute at the top-left corner of its // parent, the floating element, and move it so that it points at the // anchor, half of it outside the floating element's border box on the // side that faces the anchor. Without the [Arrow] middleware they do // nothing but make it absolute. ArrowProps tela.Prop // Placement is the placement the floating element is in, after flip or // autoPlacement chose it: one of Floating UI's 12 placements. It is the // placement given to [Placement] (default "bottom") until the first // position is computed, and is the engine's in the frame that shows the // element (the build that read the first one runs again before the frame // is painted): use it to style. Placement string // Side and Align split Placement: Side is "top", "right", "bottom" or // "left", the side of the anchor the element is on; Align is "start", // "end" or "" (centered). Side, Align string // ArrowX and ArrowY are Floating UI's arrow data: the offset of the // arrow along the floating element's edge that faces the anchor, from the // element's left edge (ArrowX, for the top and bottom sides) or top edge // (ArrowY, for left and right); the other one is 0. They are valid in the // frame where they change: read them in a paint-phase closure such as // tela.TranslateFn. Zero without the [Arrow] middleware. ArrowX, ArrowY *tela.Value[float32] // ReferenceHidden reports that the anchor is scrolled out of view (the // [Hide] middleware, strategy "referenceHidden"); Escaped that the // floating element is out of the area where the anchor can be seen // (strategy "escaped"). They are false without [Hide], and an anchor that // is not displayed counts as hidden. Read them in a paint-phase closure // to follow the same frame: // // Style(tela.OpacityFn(tela.MapValue(fl.ReferenceHidden, func(hidden bool) float32 { // if hidden { return 0 } // return 1 // }))) ReferenceHidden, Escaped *tela.Value[bool]}Floating is what UseFloating returns: the props of the floating element and of its arrow, and what the position computed.
Everything the position decides is known in the frame that shows the element: Props and ArrowProps move it and the arrow while the frame is painted, so no frame ever shows them in the wrong place. A component that styles itself by Placement builds again in that same frame, before anything is shown, when the placement differs from the one it built with (a flip), and what mounted in the frame starts its transitions again from its starting style: a menu that flips above its trigger enters from the top side’s classes in its first frame (ADR-164).
func UseFloating
Section titled “func UseFloating”func UseFloating(anchor tela.ElementRef, opts ...FloatingOption) FloatingUseFloating positions a floating element (a popover, tooltip, menu or select list) next to its anchor: Floating UI’s computePosition with its middleware, run by Tela’s frame pipeline (ADR-099). It is a hook: call it at the top level of a component.
func Popover(open bool) tela.Node { anchor := tela.UseElementRef() arrow := tela.UseElementRef() fl := aria.UseFloating(anchor, aria.Placement("bottom-start"), aria.Offset(8), aria.Flip(), aria.Shift(8), aria.Arrow(arrow)) return Fragment( Button(Ref(anchor), Text("Open")), If(open, Portal( Div(fl.Props, Class("w-64 rounded-md border bg-popover p-4 shadow-md"), Text("Content"), Div(Ref(arrow), fl.ArrowProps, Class("size-2 rotate-45 border bg-popover"))))))}The anchor is the element that carries the ref (h.Ref), or, when anchor is the zero tela.ElementRef, the virtual rectangle that AnchorRect returns, such as the pointer of a context menu. The options configure the placement and the middleware, which run in the order given, as in Floating UI’s middleware array, and the order matters: offset before flip and shift, arrow last.
The element is placed in the paint phase of the frame that shows it, on the anchor as painted: it follows its anchor when an ancestor scrolls or a transform moves it, without a frame of its own, and nothing runs while nothing changes. The options are read again on every build of the component.
Example
// A menu below its button, offset by 4 px, flipped above it when the// window has no room below and kept 8 px from the window's edges.menu := func() tela.Node { anchor := tela.UseElementRef() fl := aria.UseFloating(anchor, aria.Placement("bottom-start"), aria.Offset(4), aria.Flip(), aria.Shift(8)) return Div(Class("relative h-full"), Button(Ref(anchor), TestID("button"), Class("absolute left-4 top-[260px] h-8 w-24"), Text("Menu")), Portal(Div(fl.Props, TestID("menu"), Class("h-24 w-48 rounded-md border bg-white"), Textf("placement: %s", fl.Placement))))}app := telatest.Mount(&exampleTB{}, tela.C(menu), telatest.Size(400, 300))
button := app.Bounds(telatest.ByTestID("button"))pop := app.Bounds(telatest.ByTestID("menu"))fmt.Printf("button y %.0f to %.0f\n", button.Y, button.Bottom())fmt.Printf("menu y %.0f to %.0f, above the button\n", pop.Y, pop.Bottom())app.ExpectText("placement: top-start")// Output:// button y 260 to 292// menu y 160 to 256, above the buttontype FloatingOption
Section titled “type FloatingOption”type FloatingOption struct { // contains filtered or unexported fields}FloatingOption configures UseFloating: the placement, a virtual anchor or a middleware. Build them with Placement, AnchorRect, Offset, Flip, Shift, Size, Arrow, Hide and AutoPlacement.
func AnchorRect
Section titled “func AnchorRect”func AnchorRect(rect func() tela.Rect) FloatingOptionAnchorRect makes the anchor a virtual rectangle (Floating UI’s virtual elements), for a context menu at the pointer or a popover at a text selection. rect returns it in window coordinates (for the pointer, a Rect of zero size at its position). It runs in the paint phase of the frames that place the element; a tela.Value it reads with Get places the element again when the value changes. It is used when the anchor given to UseFloating is the zero tela.ElementRef:
pos := tela.UseValue(tela.Rect{})fl := aria.UseFloating(tela.ElementRef{}, aria.Placement("bottom-start"), aria.Flip(), aria.AnchorRect(pos.Get))Example
// A context menu at the pointer: a virtual anchor, the zero ElementRef.var pointer *tela.Value[tela.Rect]menu := func() tela.Node { pointer = tela.UseValue(tela.Rect{X: 120, Y: 80}) fl := aria.UseFloating(tela.ElementRef{}, aria.Placement("bottom-start"), aria.Flip(), aria.Shift(4), aria.AnchorRect(pointer.Get)) return Div(Class("h-full"), Portal(Div(fl.Props, TestID("menu"), Class("h-24 w-40 rounded-md bg-white"))))}app := telatest.Mount(&exampleTB{}, tela.C(menu), telatest.Size(400, 300))
r := app.Bounds(telatest.ByTestID("menu"))fmt.Printf("at the pointer: x %.0f y %.0f\n", r.X, r.Y)
pointer.Set(tela.Rect{X: 200, Y: 40})app.WaitFor("the menu to follow the pointer", func() bool { return app.Bounds(telatest.ByTestID("menu")).X == 200})r = app.Bounds(telatest.ByTestID("menu"))fmt.Printf("moved: x %.0f y %.0f\n", r.X, r.Y)// Output:// at the pointer: x 120 y 80// moved: x 200 y 40func Arrow
Section titled “func Arrow”func Arrow(ref tela.ElementRef, opts ...ArrowOptions) FloatingOptionArrow computes where the arrow element points at the anchor (Floating UI’s arrow). ref is the ref of the arrow element, attached with h.Ref next to the Floating’s ArrowProps. Give it last, after Flip and Shift.
Example
// An arrow that points at the button even though the popover shifted to// stay inside the window.tip := func() tela.Node { anchor, arrow := tela.UseElementRef(), tela.UseElementRef() fl := aria.UseFloating(anchor, aria.Offset(8), aria.Flip(), aria.Shift(8), aria.Arrow(arrow)) return Div(Class("relative h-full"), Button(Ref(anchor), TestID("button"), Class("absolute left-[340px] top-10 h-8 w-10"), Text("?")), Portal(Div(fl.Props, TestID("tip"), Class("h-16 w-48 rounded-md bg-slate-800"), Div(Ref(arrow), TestID("arrow"), fl.ArrowProps, Class("size-4 rotate-45 bg-slate-800")))))}app := telatest.Mount(&exampleTB{}, tela.C(tip), telatest.Size(400, 300))
button := app.Bounds(telatest.ByTestID("button"))arrow := app.Bounds(telatest.ByTestID("arrow"))fmt.Printf("button center x %.0f\n", button.X+button.Width/2)fmt.Printf("arrow center x %.0f\n", arrow.X+arrow.Width/2)// Output:// button center x 360// arrow center x 360func AutoPlacement
Section titled “func AutoPlacement”func AutoPlacement(opts ...AutoPlacementOptions) FloatingOptionAutoPlacement chooses the placement with the most room, without a preferred one (Floating UI’s autoPlacement). It is an alternative to Flip: do not give both.
func Flip
Section titled “func Flip”func Flip(opts ...FlipOptions) FloatingOptionFlip flips the element to the opposite side when its placement would overflow the area it must stay in (Floating UI’s flip): a popover below a button near the bottom of the window goes above it. It checks the placements in order and keeps the first that fits. Give it after Offset and before Shift.
func Hide
Section titled “func Hide”func Hide(opts ...HideOptions) FloatingOptionHide tracks whether the anchor is scrolled out of view (Floating UI’s hide), published as Floating.ReferenceHidden and Floating.Escaped.
func Offset
Section titled “func Offset”func Offset(mainAxis float32, opts ...OffsetOptions) FloatingOptionOffset moves the element mainAxis pixels away from the anchor, along the side it is on (Floating UI’s offset): the gap between a popover and its button. Give it before Flip and Shift so that they see the final distance. The options skid it along the anchor’s edge.
func Placement
Section titled “func Placement”func Placement(placement string) FloatingOptionPlacement sets the placement the floating element goes in, one of Floating UI’s 12: “top”, “top-start”, “top-end”, “right”, “right-start”, “right-end”, “bottom” (the default), “bottom-start”, “bottom-end”, “left”, “left-start” and “left-end”. The side is where the element goes relative to the anchor; “start” and “end” align it with that edge of the anchor, mirrored in a right-to-left element (h.Dir(“rtl”)), as in Floating UI. An unknown placement panics in dev mode, with the closest valid one, and is ignored otherwise.
func Shift
Section titled “func Shift”func Shift(padding float32, opts ...ShiftOptions) FloatingOptionShift keeps the element in view by sliding it along the anchor’s edge when it overflows the window (Floating UI’s shift), padding pixels from the edges. It does not change the side: give it after Flip.
func Size
Section titled “func Size”func Size(apply func(Available), opts ...SizeOptions) FloatingOptionSize gives apply the room the floating element has (Floating UI’s size), so that a long list scrolls inside the window instead of leaving it. apply calls the methods of its argument to limit the element; the limits are applied in the same frame, with no flash of the unlimited element:
aria.Size(func(a aria.Available) { a.MaxHeight(a.Height)})Put the element’s scrolling content in an element with a bounded height (a flex column with min-h-0 and overflow-y-auto), or give the floating element itself overflow-y-auto. apply runs in the paint phase: it must be quick and must not set state. A limit that apply does not ask for in a run is removed.
Example
// A long list limited to the room below its button: the popover is as// tall as the window allows, 8 px from the bottom, and scrolls.list := func() tela.Node { anchor := tela.UseElementRef() fl := aria.UseFloating(anchor, aria.Placement("bottom-start"), aria.Offset(4), aria.Size(func(a aria.Available) { a.MaxHeight(a.Height) }, aria.SizeOptions{Padding: 8})) return Div(Class("relative h-full"), Button(Ref(anchor), Class("absolute left-4 top-[50px] h-8 w-24"), Text("Open")), Portal(Div(fl.Props, TestID("list"), Class("flex w-40 flex-col overflow-y-auto bg-white"), Range(40, func(i int) tela.Node { return Div(Class("h-8 shrink-0"), Textf("Item %d", i)) }))))}app := telatest.Mount(&exampleTB{}, tela.C(list), telatest.Size(400, 300))
r := app.Bounds(telatest.ByTestID("list"))fmt.Printf("list y %.0f to %.0f of 300\n", r.Y, r.Bottom())// Output:// list y 86 to 292 of 300type FocusManager
Section titled “type FocusManager”type FocusManager struct { // contains filtered or unexported fields}FocusManager moves the focus among the focusable elements of a scope (React Aria’s FocusManager), in tree order. Hidden, disabled and inert elements, and those outside an open modal, are not candidates. Call its methods from event handlers and effects; each reports whether it found an element to focus. The zero FocusManager does nothing.
Example
t := &exampleT{}defer t.close()toolbar := func() tela.Node { scope := aria.UseFocusScope(aria.FocusScopeOptions{}) return Div(scope.Props, Role("toolbar"), AriaLabel("Format"), OnKeyDown(func(e tela.KeyboardEvent) { switch e.Key { case tela.KeyArrowRight: e.PreventDefault() scope.Manager.FocusNext(aria.FocusOptions{Wrap: true}) case tela.KeyArrowLeft: e.PreventDefault() scope.Manager.FocusPrevious(aria.FocusOptions{Wrap: true}) } }), Button(Text("Bold")), Button(Text("Italic")), Button(Text("Underline")))}app := telatest.MountFunc(t, toolbar)app.Focus(telatest.ByRole("button", "Underline"))app.Press("ArrowRight") // wraps aroundapp.ExpectFocused(telatest.ByRole("button", "Bold"))app.Press("ArrowLeft")app.ExpectFocused(telatest.ByRole("button", "Underline"))fmt.Println("the arrows move the focus")// Output: the arrows move the focusfunc (FocusManager) FocusFirst
Section titled “func (FocusManager) FocusFirst”func (m FocusManager) FocusFirst(o FocusOptions) boolFocusFirst focuses the first element of the scope.
func (FocusManager) FocusLast
Section titled “func (FocusManager) FocusLast”func (m FocusManager) FocusLast(o FocusOptions) boolFocusLast focuses the last element of the scope.
func (FocusManager) FocusNext
Section titled “func (FocusManager) FocusNext”func (m FocusManager) FocusNext(o FocusOptions) boolFocusNext focuses the element after o.From (or after the focused element) in the scope.
func (FocusManager) FocusPrevious
Section titled “func (FocusManager) FocusPrevious”func (m FocusManager) FocusPrevious(o FocusOptions) boolFocusPrevious focuses the element before o.From (or before the focused element) in the scope.
type FocusOptions
Section titled “type FocusOptions”type FocusOptions struct { // Tabbable limits the candidates to the elements in the sequential // focus order (h.TabIndex of 0 or more, Buttons and fields), leaving // out those that only a script can focus (h.TabIndex(-1)). Tabbable bool // Wrap goes around at the ends: FocusNext from the last candidate // focuses the first, FocusPrevious from the first focuses the last. // It applies to FocusNext and FocusPrevious. Wrap bool // From is where FocusNext and FocusPrevious start, an element of the // scope; the zero value starts from the focused element. From tela.ElementRef}FocusOptions configures the moves of a FocusManager.
type FocusRingOptions
Section titled “type FocusRingOptions”type FocusRingOptions struct { // Within makes the ring follow the focus anywhere inside the element, // not only on the element itself (CSS's :focus-within and // React Aria's within): a group that shows a ring around all its // parts. Within bool // Ref is the element's ref, for a hook that is used together with // others on one element (see the package documentation); the zero // value makes the hook create its own. Ref tela.ElementRef}FocusRingOptions configures UseFocusRing.
type FocusRingResult
Section titled “type FocusRingResult”type FocusRingResult struct { // Props go to the element: the focus, blur, key and pointer handlers // that keep the state, and the ref. Props tela.Prop // Focused is true while the element has the keyboard focus (with // Within: or one of its descendants does), in an active window. Focused bool // FocusVisible is true while Focused and the focus came, or was last // moved, with the keyboard: Tela's focus-visible heuristic, Chromium's // (a text field matches it whatever moved the focus). A ring drawn // from FocusVisible does not flash when a button is clicked. FocusVisible bool // Ref is the ref attached to the element by Props. Ref tela.ElementRef}FocusRingResult is what UseFocusRing returns.
func UseFocusRing
Section titled “func UseFocusRing”func UseFocusRing(o FocusRingOptions) FocusRingResultUseFocusRing returns the props that track the focus of an element, and whether it should show a focus ring (React Aria’s useFocusRing): the ring appears after the keyboard moves the focus and not after a click. The focus:, focus-visible: and focus-within: variants do the same for classes; use the hook when Go needs to know, or when a ring is made of several elements.
ring := aria.UseFocusRing(aria.FocusRingOptions{})return Button(ring.Props, Class("rounded-md px-3 py-1.5"), If(ring.FocusVisible, Class("ring-2 ring-primary")), Text("Save"))The element must be focusable (a Button, an Input, or h.TabIndex).
Example
t := &exampleT{}defer t.close()field := func() tela.Node { ring := aria.UseFocusRing(aria.FocusRingOptions{}) return Button(ring.Props, Class("px-3 py-1.5"), If(ring.FocusVisible, Text("Ring on")), If(!ring.FocusVisible, Text("Ring off")))}app := telatest.MountFunc(t, field)app.Click(telatest.ByRole("button", "Ring off")) // a click does not show the ringapp.ExpectText("Ring off")app.Press("Shift+Tab") // the keyboard doesapp.Press("Tab")app.ExpectText("Ring on")fmt.Println("the ring follows the keyboard")// Output: the ring follows the keyboardtype FocusScopeOptions
Section titled “type FocusScopeOptions”type FocusScopeOptions struct { // Contain keeps the focus inside the scope: Tab and Shift+Tab wrap // around its focusable elements instead of leaving, a press on // something that is not focusable (a scrim) leaves the focus where it // is, and a press or a script that would focus an element outside // keeps it inside instead (React Aria's contain). When the focused // element inside unmounts, the focus goes to the scope's element if it // is focusable (h.TabIndex(-1), as Radix's FocusScope focuses its // container), else to its first tabbable element. The content of // portals and overlays opened from inside the scope is inside it. The // most recently mounted containing scope wins. Contain bool // Restore gives the focus back when the scope unmounts, to the element // that had it when the scope mounted, if that element can still take // it. Restore bool // AutoFocus focuses the first tabbable element of the scope (the first // focusable one if none is tabbable) after the frame that mounts it, // unless the focus is already inside. AutoFocus bool // Ref is the element's ref, for a hook that is used together with // others on one element (see the package documentation); the zero // value makes the hook create its own. Ref tela.ElementRef}FocusScopeOptions configures UseFocusScope.
type FocusScopeResult
Section titled “type FocusScopeResult”type FocusScopeResult struct { // Props go to the element that holds the scope: the scope itself and // the ref. The scope is the element's subtree (the content of the // portals rendered from inside it counts, after its own). Props tela.Prop // Manager moves the focus inside the scope. Manager FocusManager // Ref is the ref attached to the element by Props. Ref tela.ElementRef}FocusScopeResult is what UseFocusScope returns.
func UseFocusScope
Section titled “func UseFocusScope”func UseFocusScope(o FocusScopeOptions) FocusScopeResultUseFocusScope returns the props that make an element a focus scope: contain Tab inside it (a dialog, a popover), restore the focus when it closes, focus its first element when it opens, and the manager that moves the focus inside it (React Aria’s FocusScope and useFocusManager).
scope := aria.UseFocusScope(aria.FocusScopeOptions{Contain: true, Restore: true, AutoFocus: true})return Div(scope.Props, Role("dialog"), Class("rounded-lg p-4"), ...)An arrow-key toolbar uses the manager from its key handler:
scope := aria.UseFocusScope(aria.FocusScopeOptions{})return Div(scope.Props, Role("toolbar"), OnKeyDown(func(e tela.KeyboardEvent) { switch e.Key { case tela.KeyArrowRight: e.PreventDefault() scope.Manager.FocusNext(aria.FocusOptions{Wrap: true}) case tela.KeyArrowLeft: e.PreventDefault() scope.Manager.FocusPrevious(aria.FocusOptions{Wrap: true}) }}), ...)The scope’s element holds the hook’s ref; see the package documentation when it must also hold other hooks’.
Example
t := &exampleT{}defer t.close()page := func() tela.Node { open, setOpen := tela.UseState(false) return Div( Button(OnClick(func() { setOpen.Set(true) }), Text("Open")), If(open, tela.C(settingsDialog)))}app := telatest.MountFunc(t, page)app.Click(telatest.ByRole("button", "Open"))app.ExpectFocused(telatest.ByRole("button", "First")) // AutoFocusapp.Press("Tab")app.Press("Tab")app.ExpectFocused(telatest.ByRole("button", "First")) // Contain wraps aroundfmt.Println("the focus stays inside the dialog")// Output: the focus stays inside the dialogtype HSB
Section titled “type HSB”type HSB struct{ H, S, B, A float64 }HSB is a color in the hue, saturation and brightness model (HSV, what a color picker’s area shows). H is in degrees, from 0 to 360; S and B are percentages, from 0 to 100; A is the opacity, from 0 to 1.
The model keeps what RGB forgets: the hue of a gray (S is 0) and the hue and saturation of black (B is 0). That is why the color hooks keep an HSB in their ColorState and not the tela.Color they report: a drag that passes through the gray axis does not lose the hue.
func ToHSB
Section titled “func ToHSB”func ToHSB(c tela.Color) HSBToHSB returns c in the HSB model. The color is clamped to the sRGB gamut first. The hue of a gray is 0 (RGB does not have one): a caller that holds a hue keeps it itself, as ColorState does.
Example
hsb := aria.ToHSB(tela.Hex("#336699"))fmt.Printf("hue %.0f, saturation %.0f%%, brightness %.0f%%\n", hsb.H, hsb.S, hsb.B)fmt.Println(aria.FormatHexColor(aria.HSB{H: 210, S: 66.667, B: 60, A: 1}.Color(), false))// Output:// hue 210, saturation 67%, brightness 60%// #336699func (HSB) Color
Section titled “func (HSB) Color”func (c HSB) Color() tela.ColorColor returns c as a tela.Color in the sRGB gamut. A hue of 360 is the hue of 0. Values out of range are clamped.
func (HSB) HSL
Section titled “func (HSB) HSL”func (c HSB) HSL() HSLHSL returns the same color in the HSL model.
type HSL
Section titled “type HSL”type HSL struct{ H, S, L, A float64 }HSL is a color in the hue, saturation and lightness model. H is in degrees, from 0 to 360; S and L are percentages, from 0 to 100; A is the opacity, from 0 to 1. Its saturation is not HSB’s.
func ToHSL
Section titled “func ToHSL”func ToHSL(c tela.Color) HSLToHSL returns c in the HSL model, with the clamping and the hue of ToHSB.
func (HSL) Color
Section titled “func (HSL) Color”func (c HSL) Color() tela.ColorColor returns c as a tela.Color in the sRGB gamut. A hue of 360 is the hue of 0. Values out of range are clamped.
func (HSL) HSB
Section titled “func (HSL) HSB”func (c HSL) HSB() HSBHSB returns the same color in the HSB model.
type HideOptions
Section titled “type HideOptions”type HideOptions struct { // Strategy is "referenceHidden" (the default: the anchor is out of view) // or "escaped" (the floating element is out of the area where the anchor // can be seen). Give Hide twice to track both. Strategy string // Padding is the space to keep around the area. Padding float32}HideOptions are the options of Hide.
type HoverEvent
Section titled “type HoverEvent”type HoverEvent struct { // PointerType is [PointerMouse] or [PointerPen]: a touch never hovers. PointerType PointerType // Modifier keys held during the event; Meta is the Windows, Command or // Super key. Shift, Ctrl, Alt, Meta bool}HoverEvent is passed to the callbacks of HoverOptions.
type HoverOptions
Section titled “type HoverOptions”type HoverOptions struct { // OnHoverStart is called when a mouse or a pen enters the element. OnHoverStart func(HoverEvent) // OnHoverEnd is called when it leaves, or when the element becomes // Disabled while hovered. OnHoverEnd func(HoverEvent) // OnHoverChange is called with true on a start and false on an end. OnHoverChange func(hovered bool) // Disabled turns hovering off: no callbacks, Hovered stays false. Disabled bool}HoverOptions configures UseHover.
type HoverResult
Section titled “type HoverResult”type HoverResult struct { // Props go to the element: the pointer enter and leave handlers. Props tela.Prop // Hovered is true while a mouse or a pen is over the element or one of // its descendants. Hovered bool}HoverResult is what UseHover returns.
func UseHover
Section titled “func UseHover”func UseHover(o HoverOptions) HoverResultUseHover returns the props that track whether a mouse or a pen is over the element (React Aria’s useHover). Unlike the hover: variant it reaches Go: show a tooltip, start a preview, drive an animation. It ignores touch: a finger does not hover, and the enter that a touch press makes is not a hover.
hover := aria.UseHover(aria.HoverOptions{})return Div(hover.Props, If(hover.Hovered, Class("ring-2")), Text("Card"))Like the hover: variant, the element is hovered while the pointer is over it or its descendants.
Example
t := &exampleT{}defer t.close()card := func() tela.Node { hover := aria.UseHover(aria.HoverOptions{}) return Div(hover.Props, Class("h-20 w-40"), If(hover.Hovered, Text("Hovered")), If(!hover.Hovered, Text("Idle")))}app := telatest.MountFunc(t, card)app.ExpectText("Idle")app.Hover(telatest.ByText("Idle"))app.ExpectText("Hovered")fmt.Println("hovered")// Output: hoveredtype LimitShiftOptions
Section titled “type LimitShiftOptions”type LimitShiftOptions struct { // Offset is how far the element may leave the anchor's span along the // main axis of the shift: positive keeps it that many pixels attached // (Floating UI's offset, as a number or its mainAxis). Offset float32 // CrossAxisOffset is the same along the other axis (offset.crossAxis). CrossAxisOffset float32 // NoMainAxis and NoCrossAxis turn the limit off on an axis (mainAxis: // false, crossAxis: false). NoMainAxis, NoCrossAxis bool}LimitShiftOptions are the options of LimitShift; the zero value is Floating UI’s defaults.
type ListBoxOptions
Section titled “type ListBoxOptions”type ListBoxOptions[K comparable] struct { // Keys are the keys of the items, in the order they are shown. The // list must be given on every build; a key that goes takes its // selection with it only when the caller drops it (the focus moves to // the nearest item). Keys []K // TextValue is the text of an item, for typeahead (typing "ba" focuses // the first item whose text starts with it, ignoring case). Without // it the text of an item is its key as fmt.Sprint prints it, so // typeahead works for string keys; give TextValue when the keys are // IDs, or DisallowTypeAhead to turn typeahead off. While a search is // going (it expires after one second without keys) a Space is a // character of it and selects nothing, as in React Aria. TextValue func(K) string // DisabledKeys are the items that can not be focused, selected or // acted on. They are skipped by the keyboard, and get Disabled and // AriaDisabled, so both the `disabled:` and the `aria-disabled:` // variants match. DisabledKeys []K // SelectionMode is "none" (the default: the list has actions, not a // selection), "single" or "multiple". SelectionMode string // SelectionBehavior is "toggle" (the default: a click or Space toggles // the item, so several can be selected with plain clicks) or // "replace" (a click selects only that item, Ctrl+click toggles it, // Shift+click selects a range, and moving the focus with the arrow // keys moves the selection with it). SelectionBehavior string // Selected is the selection of a controlled list, with // OnSelectionChange asking for the next one. The zero Selection leaves // the selection to the hook, which starts at DefaultSelected. See // [Selection]. Selected Selection[K] DefaultSelected Selection[K] OnSelectionChange func(Selection[K]) // DisallowEmptySelection keeps one item selected once there is one. DisallowEmptySelection bool // DisallowSelectAll turns off Ctrl+A (Cmd+A on a Mac) in a multiple // selection. DisallowSelectAll bool // OnAction is called when an item is activated: with Enter, with a // tap, or with a click when nothing is selected (SelectionBehavior // "toggle") or the list does not select (SelectionMode "none"). With // SelectionBehavior "replace" and a selection, a click selects and a // double click or Enter runs the action. OnAction func(K) // Orientation is "vertical" (the default; ArrowUp and ArrowDown move // the focus) or "horizontal" (ArrowLeft and ArrowRight). Orientation string // Direction is "ltr" (the default) or "rtl": the arrow keys of a // horizontal list swap in a right-to-left one. Direction string // ShouldFocusWrap makes ArrowDown on the last item focus the first, // and ArrowUp on the first the last. ShouldFocusWrap bool // ShouldUseVirtualFocus keeps the focus on the list itself and // marks the focused item with aria-activedescendant, as a combobox's // popup does: items are not focusable, and IsFocused(k) says which one // is highlighted. ShouldUseVirtualFocus bool // ExternalFocus is ShouldUseVirtualFocus for a list that never has // the focus: another element does (a combobox's input), and calls // [Collection.HandleKeyDown] from its key handler and puts // [Collection.ActiveDescendant] on itself. The list is not a tab stop // and pressing an item does not move the focus away from that element. // The keys of a text field stay its own: HandleKeyDown handles the // arrow keys, PageUp, PageDown and Enter, and there is no typeahead. ExternalFocus bool // ShouldFocusOnHover focuses an item when the mouse moves over it. ShouldFocusOnHover bool // DisallowTypeAhead turns off typeahead. DisallowTypeAhead bool // EscapeKeyBehavior is "clearSelection" (the default: Escape clears // the selection, and is not passed on while there is one) or "none". EscapeKeyBehavior string // AutoFocus focuses the list when it mounts: the first selected item // if there is one, else the item FocusStrategy says, else the list. AutoFocus bool // FocusStrategy is FocusFirst or FocusLast: it implies AutoFocus. FocusStrategy string // AriaLabel and AriaLabelledBy name the list. AriaLabel string AriaLabelledBy tela.ElementRef // Ref is the list's ref, for a hook that is used together with others // on one element (see the package documentation) and for a // VirtualList: the list scrolls its items into view through it. The // zero value makes the hook create its own. Ref tela.ElementRef}ListBoxOptions configures UseListBox. K is the type of the items’ keys: a string, an int, an ID type; any comparable value.
type ListBoxResult
Section titled “type ListBoxResult”type ListBoxResult[K comparable] struct { Collection[K] // Props go to the list's element: the "listbox" role, aria-multiselectable, // the ref, the key handler and, with a roving focus, a tab stop while // no item is focused. Give them to the element that scrolls (or to a // h.VirtualList). Props tela.Prop // Selected is the current selection (for a single selection, its // [Selection.First] is the selected key). Selected Selection[K] // Ref is the ref attached to the list by Props. Ref tela.ElementRef}ListBoxResult is what UseListBox returns.
func UseListBox
Section titled “func UseListBox”func UseListBox[K comparable](o ListBoxOptions[K]) ListBoxResult[K]UseListBox holds the state of a list box and returns the props of its element and its options (React Aria’s useListBox with useListState, useSelectableList and useOption): a keyed collection with a focused key, a selection, and the keyboard and pointer rules of a listbox.
var fruit = []string{"apple", "banana", "cherry"}
lb := aria.UseListBox(aria.ListBoxOptions[string]{ Keys: fruit, TextValue: strings.ToUpper, SelectionMode: "single", AriaLabel: "Fruit",})return Div(lb.Props, Class("w-48 rounded-md border p-1"), h.Range(len(fruit), func(i int) tela.Node { k := fruit[i] return Div(lb.ItemProps(k), Key(k), Class("rounded px-2 py-1 aria-selected:bg-primary aria-selected:text-primary-foreground focus-visible:ring-2"), Text(k)) }))The keyboard: ArrowUp and ArrowDown (ArrowLeft and ArrowRight for a horizontal list) move the focus, skipping disabled items, with ShouldFocusWrap going around the ends; Home and End jump; PageUp and PageDown move a viewport of items; typing text focuses the next item that starts with it (the string expires after a second); Space and Enter select or act; with a multiple selection Shift+ArrowDown extends the range, Ctrl+A selects everything and Ctrl+ArrowDown moves without selecting (with SelectionBehavior “replace”). A click selects, Shift+click extends, Ctrl+click toggles.
Style items with the aria-selected: and aria-disabled: variants, with focus-visible: (the element that has the focus), or in Go from IsSelected, IsFocused and IsDisabled. The list uses a roving focus: the focused item’s element has the focus of the window and is the list’s only tab stop. Render the items as Divs or Lis, not Buttons.
Selection
Section titled “Selection”The selection is a Selection. A list keeps it itself by default:
aria.ListBoxOptions[string]{Keys: fruit, SelectionMode: "multiple", DefaultSelected: aria.SelectionOf("banana")}and is controlled by passing the selection, next to the callback that asks for changes (OnSelectionChange is also called for an uncontrolled list):
sel, setSel := tela.UseState(aria.SelectionOf("banana"))aria.ListBoxOptions[string]{Keys: fruit, SelectionMode: "multiple", Selected: sel, OnSelectionChange: setSel.Set}Ctrl+A passes All; resolve it with Selection.Resolve to get keys.
Long lists
Section titled “Long lists”A h.VirtualList works as the list: give it Props and render its items with ItemProps. Keys that are not rendered can be focused: the list scrolls to their index and the item takes the focus when it mounts. PageUp and PageDown measure the rendered items. Typeahead and Ctrl+A work on the whole list.
h.VirtualList(len(keys), func(int) float32 { return 32 }, func(i int) tela.Node { return Div(lb.ItemProps(keys[i]), Key(keys[i]), Class("h-8"), Text(keys[i]))}, lb.Props, Class("h-64"))Sections
Section titled “Sections”Section returns the props of a group of items and of its heading, which names the group: the keys stay one flat list.
Example
names := []string{"apple", "banana", "cherry"}page := func() tela.Node { lb := aria.UseListBox(aria.ListBoxOptions[string]{ Keys: names, TextValue: strings.ToUpper, SelectionMode: "single", AriaLabel: "Fruit", }) return Div(lb.Props, Class("w-48 rounded-md border p-1"), Range(len(names), func(i int) tela.Node { k := names[i] return Div(lb.ItemProps(k), Key(k), Class("rounded px-2 py-1 aria-selected:bg-primary aria-selected:text-primary-foreground focus-visible:ring-2"), Text(k)) }))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(300, 200))app.Press("Tab")app.Press("ArrowDown")app.Press("Enter")fmt.Println(app.AriaSnapshot())// Output:// - listbox "Fruit":// - option "apple"// - option "banana" [selected]// - option "cherry"Example (controlled)
// A multiple selection that the app keeps, with the other half of the// page reading it.names := []string{"apple", "banana", "cherry"}page := func() tela.Node { sel, setSel := tela.UseState(aria.SelectionOf("banana")) lb := aria.UseListBox(aria.ListBoxOptions[string]{ Keys: names, SelectionMode: "multiple", AriaLabel: "Fruit", Selected: sel, OnSelectionChange: setSel.Set, }) return Div( Div(lb.Props, Range(len(names), func(i int) tela.Node { return Div(lb.ItemProps(names[i]), Key(names[i]), Class("h-8"), Text(names[i])) })), Textf("%d selected", sel.Resolve(names).Len()))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(300, 200))app.Click(telatest.ByRole("option", "cherry"))app.Press("Control+a")app.ExpectText("3 selected")fmt.Println("3 selected")// Output: 3 selectedtype MenuBarOptions
Section titled “type MenuBarOptions”type MenuBarOptions[K comparable] struct { // Keys are the keys of the menus, in the order their triggers are // shown. The list must be given on every build. Keys []K // DisabledKeys are the triggers that can not be focused or opened. DisabledKeys []K // Disabled disables every trigger. Disabled bool // Open points to the open menu of a controlled bar; nil leaves it to // the hook, which starts with DefaultOpen (nil: all closed). Open *K DefaultOpen *K // OnOpenChange is called with the menu that opened (open true) or the // menu that closed (open false). Moving from one menu to another calls // it once, with the one that opens. It is also called for an // uncontrolled bar. OnOpenChange func(key K, open bool) // Direction is "ltr" (the default) or "rtl", which swaps ArrowLeft and // ArrowRight. Direction string // NoWrap keeps ArrowLeft and ArrowRight from going around the ends of // the bar (the pattern wraps). NoWrap bool // AriaLabel names the bar; else it is named by AriaLabelledBy. AriaLabel string AriaLabelledBy tela.ElementRef // Ref is the bar element's ref, for a hook used together with others on // the same element; the zero value makes the hook create its own. Ref tela.ElementRef}MenuBarOptions configures UseMenuBar: a row of menu triggers, such as the File, Edit and View of an application. K is the type of the menus’ keys; the triggers are keyed like the items of a list box (see ListBoxOptions for Keys and DisabledKeys).
The open menu is a key, uncontrolled when Open is nil (the hook keeps it, starting at DefaultOpen) and controlled when Open points to it: the bar shows it and only asks OnOpenChange for the next one. A key that is not in Keys (the zero value of K, say) means that no menu is open.
type MenuBarResult
Section titled “type MenuBarResult”type MenuBarResult[K comparable] struct { // Props go to the bar's element: the "menubar" role, its orientation, // its name and the ref. Props tela.Prop // Open is whether a menu is open, and OpenKey which one (the zero value // of K when none is). Open bool OpenKey K // FocusStrategy is where the focus goes when the open menu mounts: // [FocusFirst], [FocusLast], or "" for the menu itself (opened with a // pointer). Pass it to [MenuOptions].FocusStrategy. FocusStrategy string // Close closes the open menu, if any: pass it to [MenuOptions].OnClose // so that choosing an item closes the menu. Close func() // Ref is the ref attached to the bar by Props. Ref tela.ElementRef // contains filtered or unexported fields}MenuBarResult is what UseMenuBar returns.
func UseMenuBar
Section titled “func UseMenuBar”func UseMenuBar[K comparable](o MenuBarOptions[K]) MenuBarResult[K]UseMenuBar holds the state of a menu bar and returns the props of the bar and of its triggers and menus (the WAI-ARIA menubar pattern with React Aria’s menu trigger behavior), on top of UseMenu and UsePopover. Only one menu is open at a time, so the hook is called once, and the menu that is open is rendered by a component of its own, in a Portal:
bar := aria.UseMenuBar(aria.MenuBarOptions[string]{Keys: []string{"file", "edit"}, AriaLabel: "Application"})return Fragment( Div(bar.Props, Class("flex gap-1"), h.Range(len(titles), func(i int) tela.Node { k := keys[i] return Button(bar.TriggerProps(k), Key(k), Class("rounded px-3 py-1 aria-expanded:bg-accent"), Text(titles[k])) })), Portal(If(bar.Open, tela.WithKey(appMenu{bar}, bar.OpenKey))))
type appMenu struct{ bar aria.MenuBarResult[string] }
func (m appMenu) Build() tela.Node { k := m.bar.OpenKey pop := aria.UsePopover(m.bar.PopoverOptions(k)) menu := aria.UseMenu(aria.MenuOptions[string]{ Keys: items[k], OnAction: run, OnClose: m.bar.Close, AutoFocus: true, FocusStrategy: m.bar.FocusStrategy, Ref: m.bar.MenuRef(k), }) return Div(pop.Props, m.bar.MenuProps(k), menu.Props, Class("w-48 rounded-md border bg-popover p-1"), ...)}The component has the open key as its key, so that each menu mounts anew and focuses its first item. The triggers are “menuitem”s with aria-haspopup and aria-expanded, and the bar is one tab stop that remembers its last trigger. ArrowLeft and ArrowRight (swapped by Direction “rtl”) move the focus between the triggers, wrapping unless NoWrap, and Home and End jump; ArrowDown, Enter and Space open the menu of the focused trigger on its first item, ArrowUp on its last. While a menu is open ArrowLeft and ArrowRight close it and open the neighbor’s, and hovering another trigger opens its menu: that is why the popover is not modal. A press on the open menu’s trigger closes it, so do Escape, a press outside, and Tab, which also leaves the bar. Closing gives the focus back to the trigger.
There are no submenus, no typeahead on the triggers and no mnemonics.
Example
// A menu bar: ArrowDown opens the File menu on its first item, ArrowRight// moves to the Edit menu.var ev eventsapp := telatest.MountFunc(&exampleTB{}, barPage(aria.MenuBarOptions[string]{}, &ev), telatest.Size(400, 300))app.Focus(trigger("File"))app.Press("ArrowDown")app.Press("ArrowRight")app.ExpectFocused(menuitem("Undo"))fmt.Println(app.AriaSnapshot())// Output:// - button "Before"// - menubar "Application":// - menuitem "File"// - menuitem "Edit" [expanded]// - menuitem "View"// - button "After"// - menu "Edit":// - menuitem "Undo"// - menuitem "Redo"func (MenuBarResult[K]) IsOpen
Section titled “func (MenuBarResult[K]) IsOpen”func (r MenuBarResult[K]) IsOpen(k K) boolIsOpen reports whether the menu with key k is open.
func (MenuBarResult[K]) MenuProps
Section titled “func (MenuBarResult[K]) MenuProps”func (r MenuBarResult[K]) MenuProps(k K) tela.PropMenuProps returns the props of the element of the menu with key k: its ref, aria-labelledby (the trigger), a focus scope that gives the focus back to the trigger when the menu closes, and the keys that move between the menus: ArrowLeft and ArrowRight open the neighbor’s menu and Tab closes the menu and leaves the bar. Give them next to the popover’s props and UseMenu’s.
func (MenuBarResult[K]) MenuRef
Section titled “func (MenuBarResult[K]) MenuRef”func (r MenuBarResult[K]) MenuRef(k K) tela.ElementRefMenuRef returns the ref of the menu with key k: pass it as MenuOptions.Ref; MenuBarResult.MenuProps and PopoverOptions share it.
func (MenuBarResult[K]) PopoverOptions
Section titled “func (MenuBarResult[K]) PopoverOptions”func (r MenuBarResult[K]) PopoverOptions(k K) PopoverOptionsPopoverOptions returns the options of UsePopover for the menu with key k: anchored below its trigger (“bottom-start”), open, closed through the bar, and not modal, so that the other triggers stay under the pointer: a press on any trigger is the trigger’s own and not a press outside. Change what you need in the value returned (Offset, Floating).
func (MenuBarResult[K]) SetOpen
Section titled “func (MenuBarResult[K]) SetOpen”func (r MenuBarResult[K]) SetOpen(k K, open bool)SetOpen opens the menu with key k (focusing the menu itself, as a pointer does), or closes it. It does nothing for a disabled or unknown key.
func (MenuBarResult[K]) TriggerProps
Section titled “func (MenuBarResult[K]) TriggerProps”func (r MenuBarResult[K]) TriggerProps(k K) tela.PropTriggerProps returns the props of the trigger with key k: the “menuitem” role, aria-haspopup, aria-expanded and, while its menu is open, aria-controls; the ref, the disabled state, a tab stop on the trigger that had the focus last (else the first enabled one) and the handlers: a press opens the menu (a mouse or a pen on the press, a touch on the release) or closes it, hovering opens it while another menu is open, and the keys listed in UseMenuBar work.
Render the trigger with a Button, or with any element that should behave as one.
func (MenuBarResult[K]) TriggerRef
Section titled “func (MenuBarResult[K]) TriggerRef”func (r MenuBarResult[K]) TriggerRef(k K) tela.ElementRefTriggerRef returns the ref that TriggerProps(k) attaches to the trigger with key k, to share with another hook on its element.
type MenuOptions
Section titled “type MenuOptions”type MenuOptions[K comparable] struct { Keys []K // TextValue is the text of an item, for typeahead; the key as // fmt.Sprint prints it when nil. See [ListBoxOptions]. TextValue func(K) string DisabledKeys []K // SelectionMode is "none" (the default: the items are commands, // "menuitem"), "single" (radio items, "menuitemradio", of which one // is selected) or "multiple" (checkbox items, "menuitemcheckbox"). // A single selection keeps one item selected once there is one. SelectionMode string Selected Selection[K] DefaultSelected Selection[K] OnSelectionChange func(Selection[K]) // OnAction is called with the key of an item when it is chosen (a // click, Enter or Space), whether the menu selects or not. OnAction func(K) // OnClose is called when choosing an item should close the menu: pass // the trigger's Close. See CloseOnSelect. OnClose func() // CloseOnSelect decides whether choosing an item calls OnClose: nil // (the default) closes the menu unless the selection is "multiple", // where several items are toggled in a row; point to true or false to // decide. CloseOnSelect *bool // AutoFocus focuses the menu when it mounts, and FocusStrategy // (FocusFirst, FocusLast, from the trigger's result) the item that // takes the focus; a selected item wins. A menu that mounts when it // opens passes AutoFocus: true and the trigger's FocusStrategy; a // non-empty FocusStrategy alone implies AutoFocus. AutoFocus bool FocusStrategy string // ShouldFocusWrap makes the arrow keys go around the ends. ShouldFocusWrap bool Direction string // DisallowTypeAhead turns off typeahead. DisallowTypeAhead bool // AriaLabel names the menu; the trigger's MenuProps already name it // by the trigger. AriaLabel string // Ref is the menu's ref: pass the trigger's MenuRef, which its // MenuProps attach too. Ref tela.ElementRef}MenuOptions configures UseMenu. The items are keyed like those of a list box: see ListBoxOptions for Keys, TextValue, DisabledKeys, Selected, DefaultSelected, OnSelectionChange, ShouldFocusWrap and Direction.
type MenuResult
Section titled “type MenuResult”type MenuResult[K comparable] struct { Collection[K] // Props go to the menu's element: the "menu" role, the ref, the key // handler and a tab stop for the menu itself while no item is // focused. Give them next to the trigger's MenuProps and the popover's // props. Props tela.Prop // Selected is the current selection. Selected Selection[K] // Ref is the ref attached to the menu by Props. Ref tela.ElementRef}MenuResult is what UseMenu returns.
func UseMenu
Section titled “func UseMenu”func UseMenu[K comparable](o MenuOptions[K]) MenuResult[K]UseMenu holds the state of a menu and returns the props of its element and its items (React Aria’s useMenu, useMenuItem and useMenuSection with useTreeState). It is the collection of a menu button: it takes the trigger’s results, focuses the first or last item as the trigger says, chooses with Enter, Space and clicks, closes the menu, and the focus returns to the trigger by the scope of the trigger’s MenuProps (UseMenu adds none).
Call it in the component that is rendered while the menu is open: it focuses an item when it mounts, so a menu that stays mounted while closed would not focus again.
mt := aria.UseMenuTrigger(aria.MenuTriggerOptions{})pop := aria.UsePopover(aria.PopoverOptions{ Trigger: mt.Ref, Ref: mt.MenuRef, Open: mt.Open, OnClose: mt.Close, Placement: "bottom-start", Modal: true,})return Fragment( Button(mt.TriggerProps, Text("Actions")), Portal(If(mt.Open, actionMenu{mt, pop})))
type actionMenu struct{ mt aria.MenuTriggerResult; pop aria.PopoverResult }
func (m actionMenu) Build() tela.Node { menu := aria.UseMenu(aria.MenuOptions[string]{ Keys: actions, TextValue: strings.ToUpper, OnAction: run, OnClose: m.mt.Close, AutoFocus: true, FocusStrategy: m.mt.FocusStrategy, Ref: m.mt.MenuRef, }) return Div(m.pop.Props, m.mt.MenuProps, menu.Props, Class("w-48 rounded-md border bg-popover p-1"), h.Range(len(actions), func(i int) tela.Node { return Div(menu.ItemProps(actions[i]), Key(actions[i]), Class("rounded px-2 py-1.5 focus-visible:bg-accent aria-disabled:opacity-50"), Text(actions[i])) }))}Items with SelectionMode “single” or “multiple” are radio and checkbox items: they print as menuitemradio "Name" [checked] and menuitemcheckbox "Name" [checked], and aria-checked: styles them. The mouse focuses the item under it, as in a native menu. Disabled items are skipped. Submenus are not part of this hook.
Example
// A menu button: ArrowDown opens the menu with the first item focused,// Enter chooses it, the menu closes and the focus goes back.var ev eventsapp := telatest.MountFunc(&exampleTB{}, menuApp(aria.MenuOptions[string]{}, &ev), telatest.Size(400, 300))trigger := telatest.ByRole("button", "Actions")app.Focus(trigger)app.Press("ArrowDown")fmt.Println(app.AriaSnapshot())app.Press("Enter")app.ExpectFocused(trigger)fmt.Println(ev.take())// Output:// - menu "Actions":// - menuitem "Edit"// - menuitem "Duplicate"// - menuitem "Archive"// - menuitem "Delete"// action:edittype MenuTriggerOptions
Section titled “type MenuTriggerOptions”type MenuTriggerOptions struct { // Trigger is how a pointer opens the menu: "press" (the default), at // the press of a mouse or a pen and at the release of a touch, or // "longPress", when the press lasts, which leaves the quick press of the // trigger to OnPress; the keyboard then opens it with Alt+ArrowDown and // Alt+ArrowUp only. Another value panics in dev mode. Trigger string // Type is what the trigger opens: "menu" (the default) or "listbox". Type string // Open points to the open state of a controlled menu; nil leaves the // state to the hook, starting at DefaultOpen. OnOpenChange is called // with the next state: for a controlled menu it is how the change is // asked for, for an uncontrolled one a notification. Open *bool DefaultOpen bool OnOpenChange func(open bool) // Disabled makes the trigger inert and disabled. Disabled bool // OnPress is the trigger's own press with Trigger "longPress"; it does // not run after a long press that opened the menu. OnPress func(PressEvent) // Ref is the trigger's ref and MenuRef the menu's, for hooks used // together with others on the same element; the zero value makes the // hook create its own. Ref, MenuRef tela.ElementRef // contains filtered or unexported fields}MenuTriggerOptions configures UseMenuTrigger.
type MenuTriggerResult
Section titled “type MenuTriggerResult”type MenuTriggerResult struct { // TriggerProps go to the trigger: the press (and long press), the // keyboard that opens the menu, the "button" role, a tab stop, // aria-haspopup, aria-expanded and, while open, aria-controls. TriggerProps tela.Prop // MenuProps go to the menu's element: the ref, aria-labelledby (the // trigger) and a focus scope that gives the focus back to the trigger // when the menu closes. The menu's other hooks and [UsePopover] share // MenuRef. MenuProps tela.Prop // Open is whether the menu is open. Open bool // FocusStrategy is where the focus goes when the menu opens: [FocusFirst], // [FocusLast], or "" for the menu itself (opened with a mouse or a // touch). The menu focuses accordingly when it mounts. FocusStrategy string // SetOpen asks for a state, Toggle for the other one and Close for // closed. A menu closes itself when an item is chosen by calling Close. SetOpen func(open bool) Toggle func() Close func() // Pressed is true while the trigger is pressed. Pressed bool // Ref is the trigger's ref and MenuRef the menu's. Ref, MenuRef tela.ElementRef}MenuTriggerResult is what UseMenuTrigger returns.
func UseMenuTrigger
Section titled “func UseMenuTrigger”func UseMenuTrigger(o MenuTriggerOptions) MenuTriggerResultUseMenuTrigger holds the open state of a menu and returns the props of its trigger (React Aria’s useMenuTrigger with useMenuTriggerState): a press opens it, and so do Enter, Space and ArrowDown (focusing the first item) and ArrowUp (the last), or a long press with Trigger “longPress”.
mt := aria.UseMenuTrigger(aria.MenuTriggerOptions{})pop := aria.UsePopover(aria.PopoverOptions{ Trigger: mt.Ref, Ref: mt.MenuRef, Open: mt.Open, OnClose: mt.Close, Placement: "bottom-start", Modal: true,})return Fragment( Button(mt.TriggerProps, Text("Actions")), Portal(If(mt.Open, Div(pop.Props, mt.MenuProps, Role("menu"), ...))))The menu itself, its items and keyboard navigation belong to the menu collection hook (UseMenu); this hook gives it the focus strategy and Close, to call when an item is chosen.
Example
// ArrowDown opens the menu focusing its first item, ArrowUp its last.page := func() tela.Node { mt := aria.UseMenuTrigger(aria.MenuTriggerOptions{}) pop := aria.UsePopover(aria.PopoverOptions{ Trigger: mt.Ref, Ref: mt.MenuRef, Open: mt.Open, OnClose: mt.Close, Placement: "bottom-start", Modal: true, }) return Div(Class("relative h-full"), Button(mt.TriggerProps, Class("absolute left-4 top-4 h-8 w-24"), Text("Actions")), Portal(If(mt.Open, Div(pop.Props, mt.MenuProps, Role("menu"), Class("w-40 bg-white"), Textf("focus the %s item", mt.FocusStrategy)))))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(400, 300))app.Focus(telatest.ByRole("button", "Actions"))app.Press("ArrowDown")app.ExpectText("focus the first item")app.Press("Escape")app.Press("ArrowUp")app.ExpectText("focus the last item")fmt.Println("ArrowDown: first, ArrowUp: last")// Output: ArrowDown: first, ArrowUp: lasttype MeterOptions
Section titled “type MeterOptions”type MeterOptions struct { // Value is the measure, from Min to Max. Value float64 // Min and Max are the ends of the scale. Min is 0 by default and Max // 100 when both are 0. Min, Max float64 // FormatValue returns Value as people read it, for aria-valuetext; the // default is the percentage, rounded ("40%"). FormatValue func(float64) string // AriaLabel names the meter; else it is named by the element with // LabelProps, or by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the meter, when it is not // the one with LabelProps. AriaLabelledBy tela.ElementRef}MeterOptions configures UseMeter: a measure within a known range, such as disk usage, as opposed to the progress of a task. The fields are those of ProgressBarOptions without Indeterminate.
type OffsetOptions
Section titled “type OffsetOptions”type OffsetOptions struct { // CrossAxis moves the element along the anchor's edge, so that it // skids away from the alignment (Floating UI's crossAxis). Positive // moves it right for the top and bottom sides, down for the left and // right ones (mirrored in a right-to-left element for the top and // bottom sides). CrossAxis float32 // AlignmentAxis replaces CrossAxis for aligned placements ("-start", // "-end") when HasAlignmentAxis is set: positive moves the element // toward the inside of the anchor's edge from the start and, negated, // from the end (Floating UI's alignmentAxis, which is a number or null). AlignmentAxis float32 HasAlignmentAxis bool}OffsetOptions are the options of Offset beyond the distance.
type OverlayOptions
Section titled “type OverlayOptions”type OverlayOptions struct { // Open is whether the overlay is open. A closed overlay is not on the // overlay stack: its Props do nothing. Render the overlay only while // it is open, and pass the same value here, or true when the // component that calls the hook exists only while the overlay does. Open bool // OnClose is called when the user asks to close the overlay: Escape, // a press outside it (with Dismissable) or the focus leaving it (with // ShouldCloseOnBlur). The caller closes it, by changing the state that // Open comes from. OnClose func() // Dismissable closes the overlay with a press outside it: at the // pointer down, the press is kept from the elements under it, and at // the release OnClose is called and the click the press would make is // cancelled (React Aria's isDismissable). Any mouse button counts, the // secondary and middle ones too, as in Radix's DismissableLayer (React // Aria counts the primary one only); a right click's contextmenu event // still reaches the element under the pointer after the release. An // alert dialog is not dismissable. Dismissable bool // KeyboardDismissDisabled keeps Escape from closing the overlay. The // overlay still takes the key while it is the top one: Escape does not // reach the overlays below it nor the page. KeyboardDismissDisabled bool // ShouldCloseOnBlur closes the overlay when the focus moves from // inside it to another element outside it (not when it goes nowhere, // such as the window losing the focus). ShouldCloseOnBlur bool // ShouldCloseOnInteractOutside decides, for a press outside the // overlay, whether it closes it; nil closes it for every press. The // event carries the position of the press in the window: a popover // excludes its trigger by comparing it with the trigger's bounds. It // applies to Dismissable overlays. ShouldCloseOnInteractOutside func(tela.PointerEvent) bool // Modal makes everything outside the overlay inert while it is open // and the topmost modal overlay (the overlays opened from it stay // usable): it cannot be pressed or focused, is out of the Tab order and // is hidden from the aria snapshot (React Aria's ariaHideOutside). // A dialog is modal; a popover, a menu or a toast usually is not. Modal bool}OverlayOptions configures UseOverlay.
type OverlayResult
Section titled “type OverlayResult”type OverlayResult struct { // Props go to the overlay's element: they put it on the overlay stack // while it is mounted and Open, so that Escape and presses outside // reach only the top overlay, and make it modal if asked. Props tela.Prop}OverlayResult is what UseOverlay returns.
func UseOverlay
Section titled “func UseOverlay”func UseOverlay(o OverlayOptions) OverlayResultUseOverlay returns the props that make an element an overlay (React Aria’s useOverlay and, with Modal, useModalOverlay): the topmost one is closed by Escape and by a press outside it, and a modal one makes the rest of the window inert. Overlays opened from inside another count as inside it (a popover in a dialog, in a Portal or not), so pressing in the popover does not close the dialog, and Escape closes the popover first.
func Confirm(open bool, close func()) tela.Node { ov := aria.UseOverlay(aria.OverlayOptions{Open: open, OnClose: close, Dismissable: true, Modal: true}) return Portal(Presence(If(open, Div(ov.Props, Exit("opacity-0"), Class("... transition starting:opacity-0"), ...))))}Use UseDialog on the same element for the dialog role and the focus, and UseOverlayTrigger for the state and the trigger. Rendering it inside a Presence lets it fade out: an exiting overlay is inert and no longer on the stack, so the page below is usable at once and a second Escape reaches the next overlay.
The hook has no ref: FocusScopeOptions.Ref and the other hooks that share the element take theirs as usual.
Example
// A modal overlay: Escape and a press outside close it, and the page// below is inert while it is open.page := func() tela.Node { open, setOpen := tela.UseState(true) ov := aria.UseOverlay(aria.OverlayOptions{ Open: open, OnClose: func() { setOpen.Set(false) }, Dismissable: true, Modal: true, }) return Div(Class("relative h-full"), Button(Class("h-8 w-24"), Text("Page")), If(open, Div(ov.Props, Class("absolute left-[100px] top-[100px] h-20 w-40 bg-white"), Text("Overlay"))))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(300, 200))fmt.Println(app.AriaSnapshot())app.Press("Escape")fmt.Println(app.AriaSnapshot())// Output:// - text: Overlay// - button "Page"type OverlayTriggerOptions
Section titled “type OverlayTriggerOptions”type OverlayTriggerOptions struct { // Type is what the trigger opens: "dialog" (the default), "menu", // "listbox", "tree" or "grid" (the values of aria-haspopup). Another // value panics in dev mode with the closest valid one. Type string // Open points to the open state of a controlled overlay; nil leaves the // state to the hook, starting at DefaultOpen. OnOpenChange is called // with the next state when the trigger or the overlay asks for a change: // for a controlled overlay it is how the change is asked for, for an // uncontrolled one a notification. Open *bool DefaultOpen bool OnOpenChange func(open bool) // Disabled makes the trigger inert and disabled. Disabled bool // Ref is the trigger's ref, for a hook used together with others on the // same element; the zero value makes the hook create its own. Ref tela.ElementRef // OverlayRef is the overlay's ref, which the trigger's aria-controls // points to; the zero value makes the hook create its own. The hooks of // the overlay's element (UsePopover, UseDialog, UseFocusScope...) must // share it as their Ref option. OverlayRef tela.ElementRef}OverlayTriggerOptions configures UseOverlayTrigger.
type OverlayTriggerResult
Section titled “type OverlayTriggerResult”type OverlayTriggerResult struct { // TriggerProps go to the trigger: a press that toggles the overlay, // the "button" role, a tab stop, aria-haspopup, aria-expanded and, // while the overlay is open, aria-controls. Override the role on an // element that is not a button. TriggerProps tela.Prop // OverlayProps go to the overlay's element when no other hook of the // element attaches the overlay's ref: they hold just that ref. OverlayProps tela.Prop // Open is the current state, controlled or not. Open bool // SetOpen asks for a state (a controlled overlay only reports it to // OnOpenChange), Toggle for the other one, and Close for closed; Open // for open is SetOpen(true). SetOpen func(open bool) Toggle func() Close func() // Pressed is true while the trigger is pressed. Pressed bool // Ref is the trigger's ref and OverlayRef the overlay's. Ref, OverlayRef tela.ElementRef}OverlayTriggerResult is what UseOverlayTrigger returns.
func UseOverlayTrigger
Section titled “func UseOverlayTrigger”func UseOverlayTrigger(o OverlayTriggerOptions) OverlayTriggerResultUseOverlayTrigger holds the open state of an overlay and returns the props of its trigger (React Aria’s useOverlayTrigger with useOverlayTriggerState): a press toggles the overlay, and the trigger says what it opens (aria-haspopup), whether it is open (aria-expanded) and which element it controls (aria-controls).
trig := aria.UseOverlayTrigger(aria.OverlayTriggerOptions{Type: "dialog"})ov := aria.UseOverlay(aria.OverlayOptions{Open: trig.Open, OnClose: trig.Close, Dismissable: true})return Fragment( Button(trig.TriggerProps, Class("rounded-md border px-3 py-1.5"), Text("Share")), Portal(If(trig.Open, Div(ov.Props, trig.OverlayProps, Role("dialog"), Text("Link copied")))))The state is uncontrolled by default; pass Open for a controlled one. Pair it with UsePopover or UseOverlay and UseDialog for the overlay (sharing OverlayRef), and with UseMenuTrigger for a menu.
Example
// The trigger holds the state and says what it opens and whether it is// open; OnOpenChange follows an uncontrolled overlay.page := func() tela.Node { log, setLog := tela.UseState("") trig := aria.UseOverlayTrigger(aria.OverlayTriggerOptions{ Type: "menu", OnOpenChange: func(open bool) { setLog.Set(fmt.Sprintf("open=%v", open)) }, }) return Div( Button(trig.TriggerProps, Text("Actions")), If(trig.Open, Div(Ref(trig.OverlayRef), Text("Items"))), Text(log))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(300, 200))app.Click(telatest.ByRole("button", "Actions"))app.Expect(telatest.ByRole("button", "Actions"), telatest.Expanded(true))app.ExpectText("open=true")fmt.Println("open=true")// Output: open=truetype PointerType
Section titled “type PointerType”type PointerType stringPointerType is the device or the way a press, or a hover, came from.
const ( // PointerMouse is a mouse or a touchpad. PointerMouse PointerType = "mouse" // PointerTouch is a finger on a touchscreen. PointerTouch PointerType = "touch" // PointerPen is a stylus. PointerPen PointerType = "pen" // PointerKeyboard is a press made with Enter or Space on the focused // element. PointerKeyboard PointerType = "keyboard" // PointerVirtual is a click that no pointer or key made: an assistive // technology or an agent (the "click" action of the semantics tree), // or the click a Label sends to the control it labels. PointerVirtual PointerType = "virtual")The pointer types.
type PopoverOptions
Section titled “type PopoverOptions”type PopoverOptions struct { // Trigger is the ref of the element the popover is anchored to, the // trigger that opens it ([OverlayTriggerResult].Ref). Trigger tela.ElementRef // Open is whether the popover is open, and OnClose how it is closed: // see [OverlayOptions]. Open bool OnClose func() // Placement is where the popover goes around its trigger, one of // Floating UI's 12 placements such as "bottom-start"; "bottom" by // default. It flips to the opposite side when there is no room. Placement string // Offset is the distance in pixels between the trigger and the // popover. Offset float32 // Floating are more options of [UseFloating], applied after the // popover's own (the placement, [Offset], [Flip] and [Shift] with 12 px // of room from the window's edges) and before the arrow's: a [Size] // that limits the height to the room available, say. Floating []FloatingOption // Modal makes the rest of the window inert while the popover is open, // as a menu does (React Aria's popovers are modal unless isNonModal // is set: here it is the other way round). A press on the trigger then // closes the popover, as it hits no element; a non-modal popover does // not close for the trigger's presses, which toggle it. Modal bool // KeyboardDismissDisabled keeps Escape from closing the popover. KeyboardDismissDisabled bool // ShouldCloseOnInteractOutside decides, for a press outside the popover // (and outside the popovers and dialogs opened from it), whether it // closes it; nil closes it for every press but the non-modal trigger's. ShouldCloseOnInteractOutside func(tela.PointerEvent) bool // Ref is the popover element's ref, for a hook used together with // others on the same element (the overlay trigger's OverlayRef, a // dialog); the zero value makes the hook create its own. Ref tela.ElementRef}PopoverOptions configures UsePopover.
type PopoverResult
Section titled “type PopoverResult”type PopoverResult struct { // Props go to the popover's element: it is fixed to the window and // placed next to the trigger in the frame that shows it, it is on the // overlay stack (Escape and presses outside close the top one), and // the ref. Render the element in a Portal. Props tela.Prop // ArrowProps go to an arrow element inside the popover, which points at // the trigger. ArrowProps tela.Prop // Placement is the placement the popover is in, after flipping; Side // and Align split it. They are the engine's in the frame that shows // the popover: see [Floating]. Placement, Side, Align string // Ref is the ref attached to the popover's element by Props. Ref tela.ElementRef}PopoverResult is what UsePopover returns.
func UsePopover
Section titled “func UsePopover”func UsePopover(o PopoverOptions) PopoverResultUsePopover returns the props of a popover (React Aria’s usePopover): a floating element positioned by UseFloating next to its trigger, and dismissed by UseOverlay: Escape, a press outside, or the focus going to another element closes it (never the trigger’s press or focus, which toggle it). A press inside a popover opened from it, in a Portal or not, is inside.
trig := aria.UseOverlayTrigger(aria.OverlayTriggerOptions{})pop := aria.UsePopover(aria.PopoverOptions{ Trigger: trig.Ref, Ref: trig.OverlayRef, Open: trig.Open, OnClose: trig.Close, Placement: "bottom-start", Offset: 4,})return Fragment( Button(trig.TriggerProps, Text("Filters")), Portal(Presence(If(trig.Open, Div(pop.Props, Exit("opacity-0"), Class("w-64 rounded-md border bg-popover p-4 shadow-md transition starting:opacity-0"), Div(pop.ArrowProps, Class("size-2 rotate-45 border bg-popover")), Text("Content"))))))Put UseDialog on the same element, sharing the ref, when the popover holds a form or content to be read as a dialog.
Example
// A popover 4 px below its trigger; a press outside it closes it.page := func() tela.Node { trig := aria.UseOverlayTrigger(aria.OverlayTriggerOptions{}) pop := aria.UsePopover(aria.PopoverOptions{ Trigger: trig.Ref, Ref: trig.OverlayRef, Open: trig.Open, OnClose: trig.Close, Placement: "bottom-start", Offset: 4, }) return Div(Class("relative h-full"), Button(trig.TriggerProps, Class("absolute left-4 top-4 h-8 w-24"), Text("Filters")), Portal(If(trig.Open, Div(pop.Props, TestID("pop"), Class("h-24 w-48 rounded-md border bg-white p-2"), Text("Content")))))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(400, 300))app.Click(telatest.ByRole("button", "Filters"))r := app.Bounds(telatest.ByTestID("pop"))fmt.Printf("popover at (%.0f, %.0f), %.0fx%.0f\n", r.X, r.Y, r.Width, r.Height)app.ClickAt(350, 250)app.ExpectNoText("Content")fmt.Println("closed by a press outside")// Output:// popover at (16, 52), 192x96// closed by a press outsidetype PressEvent
Section titled “type PressEvent”type PressEvent struct { // PointerType is where the press came from. PointerType PointerType // X and Y are window coordinates; LocalX and LocalY are relative to the // element's border box. X, Y, LocalX, LocalY float32 // Modifier keys held during the event; Meta is the Windows, Command or // Super key. Shift, Ctrl, Alt, Meta bool // contains filtered or unexported fields}PressEvent is passed to the callbacks of PressOptions. X and Y are the position of the event in the window, LocalX and LocalY relative to the border box of the element, in logical pixels; a press that no pointer made (keyboard, virtual) reports the center of the element.
Pressing stops the propagation of the events that make it (the pointer’s, the key’s, the click), as React Aria does, so a pressable card inside a pressable row presses only the card. Call PressEvent.ContinuePropagation in a callback to let them bubble.
func (PressEvent) ContinuePropagation
Section titled “func (PressEvent) ContinuePropagation”func (e PressEvent) ContinuePropagation()ContinuePropagation lets the event that made this press bubble to the ancestors’ handlers, instead of stopping it. It applies to the whole step of the press: if any of its callbacks continues, the event does.
Example
t := &exampleT{}defer t.close()var log []stringpage := func() tela.Node { press := aria.UsePress(aria.PressOptions{ // Let the card's own handlers see the press too. OnPressStart: func(e aria.PressEvent) { e.ContinuePropagation() }, }) return Div(OnPointerDown(func() { log = append(log, "the card saw the pointer down") }), Button(press.Props, Text("Like")))}app := telatest.MountFunc(t, page)app.Click(telatest.ByRole("button", "Like"))fmt.Println(log)// Output: [the card saw the pointer down]type PressOptions
Section titled “type PressOptions”type PressOptions struct { // OnPressStart is called when a press begins: the pointer goes down on // the element, or Space goes down on it while it is focused. A press // that left the element (without [PressOptions.ShouldCancelOnPointerExit]) // starts again if the pointer comes back while still held. OnPressStart func(PressEvent) // OnPressEnd is called when a press ends, whether it counts or not: the // release over the element, the pointer leaving it, the cancel of the // pointer or a lost focus. OnPressEnd func(PressEvent) // OnPressChange is called with true when a press starts and false when // it ends: the callback form of the Pressed field of the result. OnPressChange func(pressed bool) // OnPressUp is called when the pointer or the key is released over the // element, before OnPressEnd. OnPressUp func(PressEvent) // OnPress is called when the press completes: the release over the // element, Space released on it, Enter pressed on it, or a virtual // click. It does not run for a disabled element. OnPress func(PressEvent) // Disabled ignores every pointer, key and click on the element, and // ends a press in progress. It sets no prop: for a disabled look and // semantics use [UseButton] or h.Disabled. Disabled bool // ShouldCancelOnPointerExit ends the press for good when the pointer // leaves the element, instead of letting it start again when the // pointer comes back while pressed. ShouldCancelOnPointerExit bool // PreventFocusOnPress keeps the focus where it is when the pointer // goes down on the element (the default action of a press is to focus // the nearest focusable element). PreventFocusOnPress bool // Ref is the element's ref, for a hook that is used together with // others on one element (see the package documentation); the zero // value makes the hook create its own. Ref tela.ElementRef}PressOptions configures UsePress.
type PressResult
Section titled “type PressResult”type PressResult struct { // Props go to the element: the pointer, key, click and blur handlers // and the ref, merged. Props given after them in the element's // arguments override what they set, but handlers of the same event all // run. Props tela.Prop // Pressed is true while the element is pressed (a pointer is down on // it, or Space is held): style it with a class, since `active:` follows // the pointer only. Pressed bool // Ref is the ref attached to the element by Props. Ref tela.ElementRef}PressResult is what UsePress returns.
func UsePress
Section titled “func UsePress”func UsePress(o PressOptions) PressResultUsePress returns the props that make an element pressable, the same across mouse, touch, pen and keyboard (React Aria’s usePress).
press := aria.UsePress(aria.PressOptions{ OnPress: func(e aria.PressEvent) { save() }, Disabled: busy,})return Div(press.Props, Role("button"), TabIndex(0), Class("rounded-md px-3 py-1.5"), If(press.Pressed, Class("scale-95")), Text("Save"))A press starts when the primary button, a touch contact or a pen tip goes down on the element. It ends when the pointer is released (PressOptions.OnPress runs if that was over the element), leaves the element (which also ends it for good with PressOptions.ShouldCancelOnPointerExit) or is cancelled: the system takes the touch, a touch becomes a scroll, or the element is unmounted. The element keeps receiving the pointer while it is pressed, wherever it goes.
With the keyboard, Enter on the focused element presses it at once (OnPressStart, OnPressUp, OnPressEnd and OnPress on the key down, never on a repeat), and Space starts the press on the key down and completes it on the key up; a press by Space ends without OnPress when the focus leaves the element. The hook prevents the default action of those keys (the click of a Button, the scroll of Space). A click that no pointer made (an assistive technology, an agent, a Label) is a virtual press.
The hook stops the propagation of the events that make a press (PressEvent.ContinuePropagation lets them bubble), prevents the default action of the click (a Button does not submit its Form), and does not run for a Disabled element.
The element needs no role: UsePress adds none. For a button use UseButton, which adds the role, the tab stop and the disabled state.
Example
t := &exampleT{}defer t.close()save := func() tela.Node { saved, setSaved := tela.UseState(0) press := aria.UsePress(aria.PressOptions{ OnPress: func(e aria.PressEvent) { setSaved.Update(func(n int) int { return n + 1 }) }, }) return Div(press.Props, Role("button"), TabIndex(0), Class("rounded-md px-3 py-1.5"), If(press.Pressed, Class("scale-95")), Textf("Saved %d times", saved))}app := telatest.MountFunc(t, save)app.Click(telatest.ByRole("button", "Saved 0 times"))app.Press("Enter") // the button has the focus after the clickapp.Press("Space")app.ExpectText("Saved 3 times")fmt.Println("3 presses: mouse, Enter, Space")// Output: 3 presses: mouse, Enter, Spacetype ProgressBarOptions
Section titled “type ProgressBarOptions”type ProgressBarOptions struct { // Value is the progress, from Min to Max. It is ignored while // Indeterminate. Value float64 // Min and Max are the ends of the scale. Min is 0 by default and Max // 100 when both are 0. Min, Max float64 // Indeterminate says that the amount of progress is unknown: the bar // has no aria-valuenow, and Percent is 0. Indeterminate bool // FormatValue returns Value as people read it, for aria-valuetext; the // default is the percentage, rounded ("40%"). Use package i18n for a // locale's formatting. FormatValue func(float64) string // AriaLabel names the bar; else it is named by the element with // LabelProps, or by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the bar, when it is not // the one with LabelProps. AriaLabelledBy tela.ElementRef}ProgressBarOptions configures UseProgressBar.
type ProgressBarResult
Section titled “type ProgressBarResult”type ProgressBarResult struct { // Props go to the bar's element: its role ("progressbar" or "meter"), // aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext and its // name. Props tela.Prop // LabelProps go to the element that labels the bar. LabelProps tela.Prop // Percent is the value as a percentage of the range, from 0 to 100 // (React Aria's percentage is a fraction from 0 to 1): the width of the // fill, as Class(fmt.Sprintf("w-[%g%%]", p.Percent)) or a tela.WidthFn // of the bar's size. It is 0 for an indeterminate bar. Percent float64 // ValueText is the value as people read it (aria-valuetext); empty // for an indeterminate bar. ValueText string}ProgressBarResult is what UseProgressBar and UseMeter return.
func UseMeter
Section titled “func UseMeter”func UseMeter(o MeterOptions) ProgressBarResultUseMeter returns the props of a meter (React Aria’s useMeter): the “meter” role with its value, range and text, and its name.
m := aria.UseMeter(aria.MeterOptions{Value: used, Max: 512, AriaLabel: "Disk usage"})return Div(m.Props, Class("h-2 w-64 rounded-full bg-muted"), Div(Class("h-full bg-primary"), Class(fmt.Sprintf("w-[%g%%]", m.Percent))))Example
t := &exampleT{}defer t.close()page := func() tela.Node { m := aria.UseMeter(aria.MeterOptions{Value: 128, Max: 512, AriaLabel: "Disk usage"}) return Div(m.Props, Class("h-2 w-64 rounded-full bg-muted"), Div(Class("h-full bg-primary"), Class(fmt.Sprintf("w-[%g%%]", m.Percent))))}app := telatest.MountFunc(t, page)fmt.Println(app.AriaSnapshot())// Output: - meter "Disk usage"func UseProgressBar
Section titled “func UseProgressBar”func UseProgressBar(o ProgressBarOptions) ProgressBarResultUseProgressBar returns the props of a progress bar (React Aria’s useProgressBar): the “progressbar” role with its value, range and text, and its name. A bar with Indeterminate has no value. The caller renders the bar and its fill:
p := aria.UseProgressBar(aria.ProgressBarOptions{Value: done, AriaLabel: "Upload"})return Div(p.Props, Class("h-2 w-64 overflow-hidden rounded-full bg-muted"), Div(Class("h-full bg-primary transition-all"), Class(fmt.Sprintf("w-[%g%%]", p.Percent))))Example
t := &exampleT{}defer t.close()page := func() tela.Node { p := aria.UseProgressBar(aria.ProgressBarOptions{Value: 40, AriaLabel: "Upload"}) return Div(p.Props, Class("h-2 w-64 overflow-hidden rounded-full bg-muted"), Div(Class("h-full bg-primary"), Class(fmt.Sprintf("w-[%g%%]", p.Percent))))}app := telatest.MountFunc(t, page)fmt.Println(app.AriaSnapshot())// Output: - progressbar "Upload"type RadioGroupOptions
Section titled “type RadioGroupOptions”type RadioGroupOptions[K comparable] struct { // Keys are the keys of the radios, in the order they are shown. The // list must be given on every build. Keys []K // DisabledKeys are the radios that can not be focused or checked. // They are skipped by the arrow keys, and get Disabled and // AriaDisabled. DisabledKeys []K // Value points to the checked key of a controlled group; nil leaves // the value to the hook. Value *K // DefaultValue is the checked key of an uncontrolled group at the // start; nil checks nothing. DefaultValue *K // OnValueChange is called with the next key when the user checks a // radio: for a controlled group it is how the change is asked for, for // an uncontrolled one a notification. It is not called for the radio // that already is checked. OnValueChange func(K) // Disabled disables every radio. Disabled bool // ReadOnly keeps the radios focusable but not changeable // (aria-readonly on the group). ReadOnly bool // Required marks the group as one the user must answer // (aria-required). Required bool // Orientation is "vertical" (the default, as React Aria's) or // "horizontal". It is aria-orientation, and decides whether a // right-to-left Direction swaps ArrowLeft and ArrowRight: all four // arrows move the focus in either orientation. Orientation string // Direction is "ltr" (the default) or "rtl". Direction string // AriaLabel names the group; else it is named by the element with // LabelProps, or by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the group, when it is not // the one with LabelProps. AriaLabelledBy tela.ElementRef // Ref is the group element's ref, for a hook that is used together // with others on one element; the zero value makes the hook create // its own. Ref tela.ElementRef}RadioGroupOptions configures UseRadioGroup. K is the type of the radios’ keys: a string, an int, an enum; any comparable value.
The value is uncontrolled when Value is nil (the hook keeps it, starting at DefaultValue, or with nothing checked) and controlled when Value points to the caller’s state: the group shows it and only asks OnValueChange for the next one, so a change the caller does not store is refused. OnValueChange is also called for an uncontrolled group. A value that is not one of Keys (a controlled “” for none) checks no radio.
// Uncontrolled, starting at "pro".aria.RadioGroupOptions[string]{Keys: plans, DefaultValue: new("pro")}// Controlled.plan, setPlan := tela.UseState("pro")aria.RadioGroupOptions[string]{Keys: plans, Value: &plan, OnValueChange: setPlan.Set}type RadioGroupResult
Section titled “type RadioGroupResult”type RadioGroupResult[K comparable] struct { // Props go to the group's element: the "radiogroup" role, // aria-orientation, its name, the required, read-only and disabled // states and the ref. Props tela.Prop // LabelProps go to the element that labels the group (a span or a // Label): its text names the group, unless AriaLabel is set. LabelProps tela.Prop // Value is the checked key, valid when HasValue is true. It can be a // key that is not one of Keys when a controlled group says so. Value K // HasValue is whether the group has a value. HasValue bool // Ref is the ref attached to the group by Props. Ref tela.ElementRef // contains filtered or unexported fields}RadioGroupResult is what UseRadioGroup returns.
func UseRadioGroup
Section titled “func UseRadioGroup”func UseRadioGroup[K comparable](o RadioGroupOptions[K]) RadioGroupResult[K]UseRadioGroup holds the state of a radio group and returns the props of the group and its radios (React Aria’s useRadioGroup and useRadio with react-stately’s useRadioGroupState). At most one radio is checked; the caller renders one element per key.
plan := aria.UseRadioGroup(aria.RadioGroupOptions[string]{Keys: plans, DefaultValue: new("free"), AriaLabel: "Plan"})return Div(plan.Props, Class("grid gap-2"), h.Range(len(plans), func(i int) tela.Node { k := plans[i] return Div(Key(k), Class("flex items-center gap-2"), Button(plan.RadioProps(k), Class("size-4 rounded-full border checked:bg-primary")), Label(For(plan.RadioID(k)), Text(k))) }))The arrow keys (all four, in either orientation) move the focus to the next enabled radio, going around the ends, and check it; Space checks the focused radio; Tab enters the group at the checked radio. A press checks a radio. Right-to-left Direction swaps the horizontal arrows.
Example
t := &exampleT{}defer t.close()plans := []string{"free", "pro", "team"}page := func() tela.Node { g := aria.UseRadioGroup(aria.RadioGroupOptions[string]{Keys: plans, DefaultValue: new("free"), AriaLabel: "Plan"}) return Div(g.Props, Class("flex flex-col gap-1"), Range(len(plans), func(i int) tela.Node { k := plans[i] return Div(Key(k), Class("flex items-center gap-2"), Button(g.RadioProps(k), Class("size-4 rounded-full border checked:bg-primary")), Label(For(g.RadioID(k)), Text(k))) }))}app := telatest.MountFunc(t, page)app.Focus(telatest.ByRole("radio", "free"))app.Press("ArrowDown") // moves the focus and checksfmt.Println(app.AriaSnapshot())// Output:// - radiogroup "Plan":// - radio "free"// - text: free// - radio "pro" [checked]// - text: pro// - radio "team"// - text: teamfunc (RadioGroupResult[K]) IsSelected
Section titled “func (RadioGroupResult[K]) IsSelected”func (r RadioGroupResult[K]) IsSelected(k K) boolIsSelected reports whether the radio with key k is the checked one.
func (RadioGroupResult[K]) RadioID
Section titled “func (RadioGroupResult[K]) RadioID”func (r RadioGroupResult[K]) RadioID(k K) stringRadioID returns the id that RadioProps(k) gives the radio, for a Label’s For.
func (RadioGroupResult[K]) RadioProps
Section titled “func (RadioGroupResult[K]) RadioProps”func (r RadioGroupResult[K]) RadioProps(k K) tela.PropRadioProps returns the props of the radio with key k (React Aria’s useRadio): the “radio” role, aria-checked, an ID for a Label, the ref, a tab stop on the checked radio (the first enabled one while none is checked; the arrow keys move between the others), the disabled state and the handlers: a click, tap or Space checks it, and the arrow keys move the focus to the next radio and check it.
Render the radio with a Button: a Label(For(RadioID(k))) then names it and checks it when it is clicked. A Div works too, but a Label only names it. The checked: variant (and aria-checked:) match the checked radio.
func (RadioGroupResult[K]) RadioRef
Section titled “func (RadioGroupResult[K]) RadioRef”func (r RadioGroupResult[K]) RadioRef(k K) tela.ElementRefRadioRef returns the ref that RadioProps(k) attaches to the radio, to share with another hook on its element.
type RangeCalendarOptions
Section titled “type RangeCalendarOptions”type RangeCalendarOptions struct { // Value points to the selected range of a controlled calendar; nil // leaves it to the hook. DefaultValue starts an uncontrolled one, // and OnValueChange is called with the range the user completes (the // earlier date first). The zero DateRange selects nothing. Value *DateRange DefaultValue DateRange OnValueChange func(DateRange) // AllowsNonContiguousRanges lets a range cross unavailable dates. // Without it, once the first date is chosen the dates beyond the // nearest unavailable one on each side are unavailable too, so a // range never contains one (React Aria's rule). The nearest one is // searched three years either way. AllowsNonContiguousRanges bool
Min, Max Date IsDateUnavailable func(Date) bool Disabled bool ReadOnly bool FocusedDate *Date DefaultFocusedDate Date OnFocusChange func(Date) VisibleMonths int FirstDayOfWeek *time.Weekday FixedWeeks bool Today Date Locale string Direction string AriaLabel string AriaLabelledBy tela.ElementRef PreviousLabel string NextLabel string Ref tela.ElementRef}RangeCalendarOptions configures UseRangeCalendar. The fields shared with CalendarOptions mean the same; the value is a DateRange.
type Rects
Section titled “type Rects”type Rects struct { // Reference is the anchor's border box. Reference tela.Rect // Floating is the floating element's border box at the position the // middleware before Size left it at. Floating tela.Rect}Rects are the rectangles of the anchor and the floating element, in window coordinates.
type SectionResult
Section titled “type SectionResult”type SectionResult struct { // Props go to the element that groups the items: the "group" role, // named by the heading. Props tela.Prop // HeadingProps go to the heading's element, which only names the group // (role "presentation"). HeadingProps tela.Prop}SectionResult is what Collection.Section returns.
type SelectOptions
Section titled “type SelectOptions”type SelectOptions[K comparable] struct { Keys []K // TextValue is the text of an item: it is what the select shows when // the item is selected (SelectResult.Value) and what typeahead matches. // The default is fmt.Sprint of the key. TextValue func(K) string DisabledKeys []K Selected Selection[K] DefaultSelected Selection[K] OnSelectionChange func(Selection[K]) // Open points to the open state of a controlled select; nil leaves it // to the hook, starting at DefaultOpen. OnOpenChange is called with the // next state, as a request for a controlled select and as a // notification for an uncontrolled one. Open *bool DefaultOpen bool OnOpenChange func(open bool) // Placeholder is what SelectResult.Value says while nothing is // selected. Placeholder string // Disabled makes the trigger inert and disabled. Disabled bool // Placement is where the popover goes around the trigger, one of // Floating UI's 12 placements; "bottom-start" by default. Offset is the // distance in pixels, and Floating are more options of [UseFloating] (a // [Size] that limits the height, say), as in [PopoverOptions]. Placement string Offset float32 Floating []FloatingOption // MatchWidth makes the popover as wide as the trigger (see // SelectResult.PopoverProps). MatchWidth bool // Modal makes the rest of the window inert while the list is open, as // React Aria's select does; see [PopoverOptions]. The default is a // non-modal popover. Modal bool // DisallowTypeAhead turns off typeahead, on the trigger and in the // list. DisallowTypeAhead bool // AriaLabel names the select when there is no label element; with // LabelProps the label element names it. The trigger's name is the // value followed by the name, as in React Aria ("Apple Favorite fruit"). AriaLabel string // Ref is the trigger's ref, for a hook used together with others on the // same element; the zero value makes the hook create its own. Ref tela.ElementRef}SelectOptions configures UseSelect. The items are keyed like those of a list box: see ListBoxOptions for Keys, TextValue, DisabledKeys and the controlled and uncontrolled Selected, DefaultSelected and OnSelectionChange (a select holds one selected key).
type SelectResult
Section titled “type SelectResult”type SelectResult[K comparable] struct { // TriggerProps go to the trigger, a Button or any element: the press // that opens the list, the keys that open it (Enter, Space, ArrowDown // and ArrowUp) and change the selection while it is closed (typeahead // and ArrowLeft and ArrowRight), the "button" role, a tab stop, // aria-haspopup="listbox", aria-expanded, aria-controls while open // and aria-labelledby (the value and the label). TriggerProps tela.Prop // ValueProps go to the element inside the trigger that shows the // selected text (or the placeholder): the trigger is named by it. ValueProps tela.Prop // LabelProps go to the label element: it names the select, and a click // on it focuses the trigger. LabelProps tela.Prop // PopoverProps go to the popover's element, which holds the list // box: it is fixed to the window and placed next to the trigger in the // frame that shows it, it is on the overlay stack (Escape and a press // outside close it) and a focus scope gives the focus back to the // trigger when it unmounts. With MatchWidth it is as wide as the // trigger. Render it in a Portal, only while Open. PopoverProps tela.Prop // ArrowProps go to an arrow element inside the popover. ArrowProps tela.Prop // ListBox is the list: its Props go to the element that holds the // items (it scrolls) and ItemProps(k) to each item, as in [UseListBox]. // The items are rendered inside the popover, only while Open. ListBox ListBoxResult[K] // Value is the text of the selected item, or the Placeholder. Value string // Selected is the current selection: [Selection.First] is the // selected key. Selected Selection[K] // Open is whether the list is open; SetOpen asks for a state, Toggle for // the other one and Close for closed. Open bool SetOpen func(open bool) Toggle func() Close func() // Pressed is true while the trigger is pressed. Pressed bool // Placement is the popover's placement after flipping (see // [PopoverResult]). Placement string // TriggerRef and PopoverRef are the refs of the trigger and the // popover. TriggerRef, PopoverRef tela.ElementRef}SelectResult is what UseSelect returns.
func UseSelect
Section titled “func UseSelect”func UseSelect[K comparable](o SelectOptions[K]) SelectResult[K]UseSelect holds the state of a select and returns the props of its trigger, its popover and its list (React Aria’s useSelect with useSelectState): a button that shows the selected item and opens a list box next to it. A press, Enter, Space and ArrowDown open the list (the last also with ArrowUp), the selected item or else the first (the last for ArrowUp) has the focus, choosing an item selects it, closes the list and gives the focus back to the trigger, and Escape closes it. On the closed trigger typing text selects the next item that starts with it and the left and right arrows select the previous and next item.
sel := aria.UseSelect(aria.SelectOptions[string]{ Keys: fruit, TextValue: capital, Placeholder: "Pick a fruit", MatchWidth: true,})return Fragment( Span(sel.LabelProps, Text("Favorite fruit")), Button(sel.TriggerProps, Class("flex w-48 justify-between rounded-md border px-3 py-2"), Span(sel.ValueProps, Text(sel.Value)), Icon(chevronDown)), Portal(If(sel.Open, Div(sel.PopoverProps, Class("rounded-md border bg-popover p-1 shadow-md"), Div(sel.ListBox.Props, Class("max-h-60 overflow-auto"), Range(len(fruit), func(i int) tela.Node { k := fruit[i] return Div(sel.ListBox.ItemProps(k), Key(k), Class("rounded px-2 py-1.5 focus-visible:bg-accent aria-selected:font-medium"), Text(capital(k))) }))))))In the aria snapshot the closed select is a button named by its value and its label (button "Banana Favorite fruit": Banana), and the open list, a listbox named by the label, sits next to it.
The list lives in the component that calls the hook (its items are rendered by the caller only while open): the hook focuses the selected item when the list opens. There is no hidden native select, so a Form does not submit the value: read SelectResult.Selected in OnSubmit.
Example
t := &exampleT{}defer t.close()names := []string{"apple", "banana", "cherry"}page := func() tela.Node { sel := aria.UseSelect(aria.SelectOptions[string]{ Keys: names, TextValue: capital, Placeholder: "Pick a fruit", MatchWidth: true, }) return Div(Class("flex flex-col gap-1 p-4"), Span(sel.LabelProps, Class("text-sm font-medium"), Text("Favorite fruit")), Button(sel.TriggerProps, Class("flex w-48 justify-between rounded-md border px-3 py-2"), Span(sel.ValueProps, Text(sel.Value))), Portal(If(sel.Open, Div(sel.PopoverProps, Class("rounded-md border bg-popover p-1 shadow-md"), Div(sel.ListBox.Props, Class("max-h-60 overflow-auto"), Range(len(names), func(i int) tela.Node { k := names[i] return Div(sel.ListBox.ItemProps(k), Key(k), Class("rounded px-2 py-1.5 focus-visible:bg-accent aria-selected:font-medium"), Text(capital(k))) }))))))}app := telatest.MountFunc(t, page, telatest.Size(400, 300))fmt.Println(app.AriaSnapshot())app.Focus(telatest.ByRole("button", "Pick a fruit Favorite fruit"))app.Press("ArrowDown") // opens it on the first itemapp.Press("ArrowDown")fmt.Println(app.AriaSnapshot())app.Press("Enter") // selects Banana, closes and gives the focus backfmt.Println(app.AriaSnapshot())// Output:// - text: Favorite fruit// - button "Pick a fruit Favorite fruit": Pick a fruit// - text: Favorite fruit// - button "Pick a fruit Favorite fruit" [expanded]: Pick a fruit// - listbox "Favorite fruit":// - option "Apple"// - option "Banana"// - option "Cherry"// - text: Favorite fruit// - button "Banana Favorite fruit": BananaExample (controlled)
t := &exampleT{}defer t.close()names := []string{"apple", "banana", "cherry"}page := func() tela.Node { sel, setSel := tela.UseState(aria.SelectionOf("banana")) s := aria.UseSelect(aria.SelectOptions[string]{ Keys: names, TextValue: capital, AriaLabel: "Fruit", Selected: sel, OnSelectionChange: setSel.Set, }) return Div( Button(s.TriggerProps, Span(s.ValueProps, Text(s.Value))), Portal(If(s.Open, Div(s.PopoverProps, Div(s.ListBox.Props, Range(len(names), func(i int) tela.Node { return Div(s.ListBox.ItemProps(names[i]), Key(names[i]), Text(capital(names[i]))) }))))))}app := telatest.MountFunc(t, page, telatest.Size(400, 300))app.Click(telatest.ByRole("button", "Banana Fruit"))app.Click(telatest.ByRole("option", "Cherry"))fmt.Println(app.AriaSnapshot())// Output: - button "Cherry Fruit": Cherrytype Selection
Section titled “type Selection”type Selection[K comparable] struct { // contains filtered or unexported fields}Selection is an immutable set of keys, the selection of the collection hooks (UseListBox, UseMenu and UseTabs): an explicit set of keys or All, React Aria’s “all”. Its methods never change the receiver, so a Selection can be kept in tela.UseState and shared freely.
sel := aria.SelectionOf("b", "c")sel.Has("b") // truesel.With("a").Keys() // [b c a]sel.Without("b").Keys() // [c]The zero Selection is “not given”: a hook option left at it is uncontrolled, and the hook keeps the selection itself. Pass SelectionOf with no keys for a controlled, empty selection:
// Uncontrolled: the hook keeps the selection.aria.ListBoxOptions[string]{Keys: keys, SelectionMode: "multiple", DefaultSelected: aria.SelectionOf("b")}// Controlled: the app keeps it.sel, setSel := tela.UseState(aria.SelectionOf("b"))aria.ListBoxOptions[string]{Keys: keys, SelectionMode: "multiple", Selected: sel, OnSelectionChange: setSel.Set}A Selection is comparable with == (it holds a pointer), but two selections with the same keys are not == unless they are the same value: use Selection.Equal to compare keys.
Example
sel := aria.SelectionOf("banana", "cherry")fmt.Println(sel.Has("banana"), sel.Has("apple"))fmt.Println(sel.With("apple").Without("cherry"))// Keys listed in the order of a collection:fmt.Println(sel.In([]string{"apple", "banana", "cherry", "date"}))// React Aria's "all", resolved against the keys:fmt.Println(aria.All[string]().Resolve([]string{"apple", "banana"}))// Output:// true false// {banana apple}// [banana cherry]// {apple banana}func All
Section titled “func All”func All[K comparable]() Selection[K]All returns the selection of every key that can be selected: React Aria’s “all”. A collection hook shows every enabled key selected; its keys are not listed in the value, so Selection.Len is 0 and Selection.Keys empty for it: use Selection.Resolve to turn it into the explicit keys of a collection.
func SelectionOf
Section titled “func SelectionOf”func SelectionOf[K comparable](keys ...K) Selection[K]SelectionOf returns the selection of exactly the given keys (duplicates count once). With no keys it is the empty selection, which, unlike the zero Selection, is a value: it makes a controlled hook show nothing selected.
func (Selection[K]) Equal
Section titled “func (Selection[K]) Equal”func (s Selection[K]) Equal(o Selection[K]) boolEqual reports whether s and o select the same keys, whatever their order: two All are equal, an All and an explicit selection are not, and the zero Selection equals only itself.
func (Selection[K]) First
Section titled “func (Selection[K]) First”func (s Selection[K]) First() (K, bool)First returns the first key of an explicit selection, in the order the keys were added, and whether there is one: the key of a single selection.
func (Selection[K]) Has
Section titled “func (Selection[K]) Has”func (s Selection[K]) Has(k K) boolHas reports whether k is selected. For All it is always true (a hook still never selects a disabled key).
func (Selection[K]) In
Section titled “func (Selection[K]) In”func (s Selection[K]) In(order []K) []KIn returns the selected keys among order, in the order of order: the selection of a collection listed in the order of its keys. For All it is every key of order.
func (Selection[K]) IsAll
Section titled “func (Selection[K]) IsAll”func (s Selection[K]) IsAll() boolIsAll reports whether s is All.
func (Selection[K]) IsEmpty
Section titled “func (Selection[K]) IsEmpty”func (s Selection[K]) IsEmpty() boolIsEmpty reports whether no key is selected: the zero Selection and SelectionOf with no keys. All is not empty.
func (Selection[K]) IsSet
Section titled “func (Selection[K]) IsSet”func (s Selection[K]) IsSet() boolIsSet reports whether s is a selection. The zero Selection is not: it means “none given” in the options of the hooks (uncontrolled).
func (Selection[K]) Keys
Section titled “func (Selection[K]) Keys”func (s Selection[K]) Keys() []KKeys returns the keys of an explicit selection in the order they were added (nil for All and the zero Selection). The slice is a copy. Use Selection.In to list them in the order of a collection.
func (Selection[K]) Len
Section titled “func (Selection[K]) Len”func (s Selection[K]) Len() intLen returns the number of keys of an explicit selection. All has no count of its own and returns 0: use len(s.In(keys)) for the number of selected items, or resolve it with Selection.Resolve.
func (Selection[K]) Resolve
Section titled “func (Selection[K]) Resolve”func (s Selection[K]) Resolve(order []K) Selection[K]Resolve returns s as an explicit selection of keys among order: for All the keys of order, for an explicit selection s itself.
func (Selection[K]) String
Section titled “func (Selection[K]) String”func (s Selection[K]) String() stringString returns “all”, “unset” for the zero Selection, or the keys in braces.
func (Selection[K]) Toggle
Section titled “func (Selection[K]) Toggle”func (s Selection[K]) Toggle(k K) Selection[K]Toggle returns s without k when it has it and with k when it does not.
func (Selection[K]) With
Section titled “func (Selection[K]) With”func (s Selection[K]) With(keys ...K) Selection[K]With returns s with the keys added. Adding to All returns it as is.
func (Selection[K]) Without
Section titled “func (Selection[K]) Without”func (s Selection[K]) Without(keys ...K) Selection[K]Without returns s with the keys removed. Removing from All returns it as is, since it does not list its keys: resolve it first.
type ShiftLimiter
Section titled “type ShiftLimiter”type ShiftLimiter struct { // contains filtered or unexported fields}ShiftLimiter limits how far Shift moves the element. Build one with LimitShift; the zero value limits nothing.
func LimitShift
Section titled “func LimitShift”func LimitShift(opts ...LimitShiftOptions) ShiftLimiterLimitShift returns the limiter that keeps the element attached to the anchor while it shifts: the element stops following the window’s edge when it would no longer touch the anchor (Floating UI’s limitShift).
aria.Shift(8, aria.ShiftOptions{Limiter: aria.LimitShift()})type ShiftOptions
Section titled “type ShiftOptions”type ShiftOptions struct { // NoMainAxis stops the shift along the side's axis (the one that keeps // the element in view: x for the top and bottom sides; Floating UI's // mainAxis: false). NoMainAxis bool // CrossAxis also shifts it along the other axis, which would move it // into the anchor (crossAxis: true). CrossAxis bool // Limiter limits the shift; see [LimitShift]. Limiter ShiftLimiter}ShiftOptions are the options of Shift; the zero value is Floating UI’s defaults.
type SizeOptions
Section titled “type SizeOptions”type SizeOptions struct { // Padding is the space to keep between the element and the edges of // the area it must stay in. Padding float32}SizeOptions are the options of Size.
type SliderOptions
Section titled “type SliderOptions”type SliderOptions struct { // Values points to the values of a controlled slider, one per thumb; // nil leaves them to the hook. Values *[]float64 // LiveValues, for a controlled slider whose values change between // builds without building it (a seek bar a clock moves), returns the // values as they are now. The slider reads it at the start of a press // on the track or on a thumb and of a key, in place of the Values of // its last build, so a change it asks for is compared with what the // caller really has. It is called on the UI thread, in handlers, and // not from paint-phase closures: they place the thumbs from what the // caller reads itself (see [SliderResult.ThumbPercent]). Nil reads // only the Values of each build. LiveValues func() []float64 // DefaultValues are the values of an uncontrolled slider at the start. DefaultValues []float64 // OnValuesChange is called with all the values (a copy) every time the // user moves a thumb to a new value: while dragging, and on each key. OnValuesChange func([]float64) // OnChangeEnd is called with the values (a copy) when the user stops // changing them: the pointer is released, or a key changed a value. OnChangeEnd func([]float64) // Min and Max are the ends of the track. Min is 0 by default and Max // 100 when both are 0. Min, Max float64 // Step is the distance between the values the thumbs can take, from // Min; 1 by default. Values are rounded to it. Step float64 // PageSize is how far PageUp, PageDown and Shift with an arrow move a // thumb, rounded to Step; a tenth of the range by default. PageSize float64 // MinStepsBetweenThumbs is the least number of Steps between two // thumbs; 0 lets them touch. MinStepsBetweenThumbs int // Orientation is "horizontal" (the default; Min is at the left, or at // the right with a "rtl" Direction) or "vertical" (Min at the bottom). Orientation string // Direction is "ltr" (the default) or "rtl", which mirrors a // horizontal slider and its arrow keys. Direction string // Disabled makes the thumbs inert and disabled, and the track ignores // presses. Disabled bool // FormatValue returns a value as people read it, for aria-valuetext // and [SliderResult.ValueText]; the default is the number as it is // written (3, 0.25). Use package i18n for a locale's digits and // units. FormatValue func(float64) string // AriaLabel names the slider; else it is named by the element with // LabelProps, or by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the slider, when it is not // the one with LabelProps. AriaLabelledBy tela.ElementRef // ThumbLabels name each thumb (aria-label), for a range slider whose // thumbs are "Minimum price" and "Maximum price"; a thumb without one // takes the slider's name. ThumbLabels []string}SliderOptions configures UseSlider: one thumb, or several (a range slider has two) on a track from Min to Max.
The values are uncontrolled when Values is nil (the hook keeps them, starting at DefaultValues) and controlled when Values points to the caller’s state: the slider shows it and only asks OnValuesChange for the next one, so a change the caller does not store is refused. OnValuesChange is also called for an uncontrolled slider. The number of thumbs is the length of the values (one at Min when DefaultValues is empty), and the values are sorted by the thumbs: a thumb stops at its neighbor.
// A range slider that keeps its values.aria.SliderOptions{DefaultValues: []float64{20, 80}, Max: 100, Step: 5}// A controlled single slider.vol, setVol := tela.UseState([]float64{30})aria.SliderOptions{Values: &vol, OnValuesChange: setVol.Set}A slider with one thumb is a slice of one value, as in Radix and shadcn/ui; SliderResult.Values has it at index 0.
type SliderResult
Section titled “type SliderResult”type SliderResult struct { // Props go to the slider's root element: the "group" role and its // name. Props tela.Prop // TrackProps go to the track: a press on it moves the nearest thumb to // the point and starts dragging it, with a mouse, a finger or a pen. TrackProps tela.Prop // LabelProps go to the element that labels the slider (a span or a // Label): its text names the slider and its thumbs, unless AriaLabel // or ThumbLabels do. LabelProps tela.Prop // Values are the values of the thumbs as of this build. While a thumb // is dragged in an uncontrolled slider they are brought up to date at // the end of the drag; read ThumbPercent and ValueText for the live // ones. Values []float64 // Dragging is whether a thumb is being dragged, as of this build. Dragging bool // TrackRef is the ref that TrackProps attaches to the track. TrackRef tela.ElementRef // contains filtered or unexported fields}SliderResult is what UseSlider returns. It is the result of one build: call its methods in the build or in handlers; ThumbPercent is also made to be called in the paint-phase closures of the thumbs.
func UseSlider
Section titled “func UseSlider”func UseSlider(o SliderOptions) SliderResultUseSlider holds the state of a slider and returns the props of its root, track and thumbs (React Aria’s useSlider, useSliderThumb and react-stately’s useSliderState). The caller renders the track, the thumbs and the fill between them with whatever markup and classes it wants:
s := aria.UseSlider(aria.SliderOptions{DefaultValues: []float64{40}, AriaLabel: "Volume"})track := tela.UseSize(s.TrackRef)return Div(s.Props, Class("flex h-5 w-64 items-center"), Div(s.TrackProps, Class("relative h-2 flex-1 rounded-full bg-muted"), Div(s.ThumbProps(0), Class("absolute -top-1.5 -ml-2.5 size-5 rounded-full bg-primary"), Style(tela.TranslateFn(func() (x, y float32) { return float32(s.ThumbPercent(0)) / 100 * track.Get().Width, 0 })))))Dragging moves a thumb by repainting only: the thumb’s position is read from ThumbPercent in a paint-phase closure, and an uncontrolled slider builds when the drag starts and ends, not on every move (a controlled one builds as often as the caller’s state changes). A press on the track moves the nearest thumb to the point and goes on dragging it. A thumb never passes its neighbor (nor comes closer than MinStepsBetweenThumbs).
Example
t := &exampleT{}defer t.close()page := func() tela.Node { s := aria.UseSlider(aria.SliderOptions{DefaultValues: []float64{40}, AriaLabel: "Volume"}) track := tela.UseSize(s.TrackRef) return Div(s.Props, Class("w-64 p-4"), Div(s.TrackProps, Class("relative h-2 w-full rounded-full bg-muted"), Div(s.ThumbProps(0), Class("absolute -top-1.5 -ml-2.5 size-5 rounded-full bg-primary"), Style(tela.TranslateFn(func() (x, y float32) { return float32(s.ThumbPercent(0)) / 100 * track.Get().Width, 0 })))))}app := telatest.MountFunc(t, page)vol := telatest.ByRole("slider", "Volume")app.Focus(vol)app.Press("PageUp") // a tenth of the rangeapp.Press("ArrowLeft")fmt.Println(app.AriaSnapshot())app.Expect(vol, telatest.HasValue("49"))// Output:// - group "Volume":// - slider "Volume"func (SliderResult) IsThumbDragging
Section titled “func (SliderResult) IsThumbDragging”func (r SliderResult) IsThumbDragging(i int) boolIsThumbDragging reports whether thumb i is being dragged now.
func (SliderResult) ThumbPercent
Section titled “func (SliderResult) ThumbPercent”func (r SliderResult) ThumbPercent(i int) float64ThumbPercent returns the position of thumb i along the track, from 0 (Min) to 100 (Max), as it is now. It subscribes what reads it: read it in a paint-phase closure (tela.TranslateFn, tela.WidthFn) to place the thumb while it is dragged without building the component, as
Style(tela.TranslateFn(func() (x, y float32) { return float32(s.ThumbPercent(i)) / 100 * track.Get().Width, 0}))where track is tela.UseSize(s.TrackRef). Read in the component’s body it makes the component build on every move.
func (SliderResult) ThumbProps
Section titled “func (SliderResult) ThumbProps”func (r SliderResult) ThumbProps(i int) tela.PropThumbProps returns the props of thumb i (React Aria’s useSliderThumb): the “slider” role, aria-valuenow, aria-valuemin and aria-valuemax (the limits of the thumb: its neighbors), aria-valuetext, aria-orientation, its name, the ref, a tab stop, and the handlers: a press on the thumb drags it (the pointer keeps it while it is down, wherever it goes), the arrow keys move it by one Step (by a page with Shift), PageUp and PageDown by a page (a tenth of the range, rounded to Step), Home and End to its limits. Right-to-left Direction mirrors the horizontal arrows.
func (SliderResult) ThumbRef
Section titled “func (SliderResult) ThumbRef”func (r SliderResult) ThumbRef(i int) tela.ElementRefThumbRef returns the ref that ThumbProps(i) attaches to thumb i.
func (SliderResult) ValueText
Section titled “func (SliderResult) ValueText”func (r SliderResult) ValueText(i int) stringValueText returns the value of thumb i as people read it: FormatValue of the live value.
type TabsOptions
Section titled “type TabsOptions”type TabsOptions[K comparable] struct { Keys []K DisabledKeys []K // Orientation is "horizontal" (the default; ArrowLeft and ArrowRight // move between tabs) or "vertical" (ArrowUp and ArrowDown). Orientation string // KeyboardActivation is "automatic" (the default: the tab that has the // focus is selected, so the arrows change the panel) or "manual" (the // arrows move the focus and Enter or Space selects). KeyboardActivation string // Selected is the selected tab of a controlled list, as a [Selection] // of one key; the zero Selection leaves it to the hook, which starts // at DefaultSelected. A tab list always has one selected tab: the // first enabled one when none is given or the given one is missing or // disabled. Selected Selection[K] DefaultSelected Selection[K] OnSelectionChange func(Selection[K]) Direction string AriaLabel string AriaLabelledBy tela.ElementRef // Ref is the tab list's ref, for a hook that is used together with // others on one element; the zero value makes the hook create its own. Ref tela.ElementRef}TabsOptions configures UseTabs. The tabs are keyed like the items of a list box: see ListBoxOptions for Keys, DisabledKeys, Selected, DefaultSelected, OnSelectionChange, Direction, AriaLabel and AriaLabelledBy. A tab list has no typeahead, as in React Aria.
type TabsResult
Section titled “type TabsResult”type TabsResult[K comparable] struct { Collection[K] // Props go to the tab list's element: the "tablist" role, // aria-orientation, the ref and the key handler. Props tela.Prop // Selected is the selected tab (its [Selection.First] is the key). Selected Selection[K] // Ref is the ref attached to the tab list by Props. Ref tela.ElementRef}TabsResult is what UseTabs returns.
func UseTabs
Section titled “func UseTabs”func UseTabs[K comparable](o TabsOptions[K]) TabsResult[K]UseTabs holds the state of a tab list and returns the props of the list, its tabs and its panels (React Aria’s useTabList, useTab and useTabPanel with useTabListState).
tabs := aria.UseTabs(aria.TabsOptions[string]{Keys: names, AriaLabel: "Settings"})sel, _ := tabs.Selected.First()return Div( Div(tabs.Props, Class("flex gap-1 border-b"), h.Range(len(names), func(i int) tela.Node { k := names[i] return Div(tabs.TabProps(k), Key(k), Class("px-3 py-2 aria-selected:border-b-2 aria-selected:border-primary focus-visible:ring-2"), Text(k)) })), Div(tabs.PanelProps(sel), Class("p-4"), panel(sel)))The arrow keys move the focus between tabs and go around the ends; Home and End jump; with the default automatic activation the focused tab is selected, with KeyboardActivation “manual” Enter or Space selects it. Disabled tabs are skipped. Only the selected tab is a tab stop: Tab goes from it to its panel.
Example
names := []string{"account", "password"}page := func() tela.Node { tabs := aria.UseTabs(aria.TabsOptions[string]{Keys: names, AriaLabel: "Settings"}) sel, _ := tabs.Selected.First() return Div( Div(tabs.Props, Class("flex gap-1"), Range(len(names), func(i int) tela.Node { k := names[i] return Div(tabs.TabProps(k), Key(k), Class("px-3 py-2 aria-selected:border-b-2"), Text(k)) })), Div(tabs.PanelProps(sel), Class("p-4"), Textf("%s settings", sel)))}app := telatest.MountFunc(&exampleTB{}, page)app.Press("Tab")app.Press("ArrowRight")fmt.Println(app.AriaSnapshot())// Output:// - tablist "Settings":// - tab "account"// - tab "password" [selected]// - tabpanel "password": password settingsfunc (TabsResult[K]) PanelProps
Section titled “func (TabsResult[K]) PanelProps”func (r TabsResult[K]) PanelProps(k K) tela.PropPanelProps returns the props of the panel of the tab with key k: the “tabpanel” role, named by the tab, the ref that the tab’s aria-controls points to, and a tab stop (give the panel TabIndex(-1) after Props when it holds focusable content of its own). Render the panel of the selected tab only.
func (TabsResult[K]) TabProps
Section titled “func (TabsResult[K]) TabProps”func (r TabsResult[K]) TabProps(k K) tela.PropTabProps returns the props of the tab with key k: the “tab” role, aria-selected, aria-controls (the panel, while selected), the ref, a tab stop on the selected tab (the arrows move between the others) and the handlers. It is Collection.ItemProps.
type ToggleGroupOptions
Section titled “type ToggleGroupOptions”type ToggleGroupOptions[K comparable] struct { // Keys are the keys of the items, in the order they are shown. The // list must be given on every build. Keys []K // DisabledKeys are the items that can not be focused or toggled. DisabledKeys []K // Disabled disables every item. Disabled bool // SelectionMode is "single" (the default: at most one item is on, and // the group is a radio group of toggle buttons) or "multiple" (any // number is on, and the group is a toolbar). SelectionMode string // Selected is the selection of a controlled group; the zero Selection // leaves it to the hook. In "single" mode its first key is the one that // is on. Selected Selection[K] // DefaultSelected is the selection of an uncontrolled group at the // start. DefaultSelected Selection[K] // OnSelectionChange is called with the next selection when the user // toggles an item. An equal selection is not reported. OnSelectionChange func(Selection[K]) // DisallowEmptySelection keeps one item on: toggling the only item that // is on does nothing. DisallowEmptySelection bool // Orientation is "horizontal" (the default; ArrowLeft and ArrowRight // move the focus) or "vertical" (ArrowUp and ArrowDown). Orientation string // Direction is "ltr" (the default) or "rtl", which swaps the // horizontal arrows. Direction string // ShouldFocusWrap makes the arrow keys go around the ends of the group. ShouldFocusWrap bool // AriaLabel names the group; else it is named by AriaLabelledBy. AriaLabel string // AriaLabelledBy is the element that names the group. AriaLabelledBy tela.ElementRef // Ref is the group element's ref, for a hook that is used together // with others on one element; the zero value makes the hook create // its own. Ref tela.ElementRef}ToggleGroupOptions configures UseToggleGroup: a row of toggle buttons with one value, such as a segmented control or a formatting toolbar. The items are keyed like those of a list box: see ListBoxOptions for Keys and DisabledKeys.
The selection is a Selection, uncontrolled when Selected is the zero Selection (the hook keeps it, starting at DefaultSelected) and controlled when Selected is given: the group shows it and only asks OnSelectionChange for the next one. OnSelectionChange is also called for an uncontrolled group.
type ToggleGroupResult
Section titled “type ToggleGroupResult”type ToggleGroupResult[K comparable] struct { // Props go to the group's element: the role ("radiogroup" for a single // selection, "toolbar" for multiple), aria-orientation, its name and // the ref. Props tela.Prop // Selected is the current selection, controlled or not. Selected Selection[K] // Ref is the ref attached to the group by Props. Ref tela.ElementRef // contains filtered or unexported fields}ToggleGroupResult is what UseToggleGroup returns.
func UseToggleGroup
Section titled “func UseToggleGroup”func UseToggleGroup[K comparable](o ToggleGroupOptions[K]) ToggleGroupResult[K]UseToggleGroup holds the state of a group of toggle buttons and returns the props of the group and its items (React Aria’s useToggleButtonGroup and useToggleButtonGroupItem). It is the segmented control of a kit:
view := aria.UseToggleGroup(aria.ToggleGroupOptions[string]{ Keys: []string{"list", "grid"}, DefaultSelected: aria.SelectionOf("list"), DisallowEmptySelection: true, AriaLabel: "View",})return Div(view.Props, Class("inline-flex rounded-md border"), h.Range(len(modes), func(i int) tela.Node { k := modes[i] return Button(view.ItemProps(k), Key(k), Class("px-3 py-1 aria-checked:bg-accent"), Text(k)) }))The group is a single tab stop, and the arrows move the focus between its items without toggling them, skipping the disabled ones; Home and End jump. With SelectionMode “single” pressing the item that is on turns it off, unless DisallowEmptySelection.
Example
t := &exampleT{}defer t.close()modes := []string{"list", "grid"}page := func() tela.Node { g := aria.UseToggleGroup(aria.ToggleGroupOptions[string]{ Keys: modes, DefaultSelected: aria.SelectionOf("list"), DisallowEmptySelection: true, AriaLabel: "View", }) return Div(g.Props, Class("inline-flex rounded-md border"), Range(len(modes), func(i int) tela.Node { return Button(g.ItemProps(modes[i]), Key(modes[i]), Class("px-3 py-1 aria-checked:bg-accent"), Text(modes[i])) }))}app := telatest.MountFunc(t, page)app.Click(telatest.ByRole("radio", "grid"))fmt.Println(app.AriaSnapshot())// Output:// - radiogroup "View":// - radio "list"// - radio "grid" [checked]func (ToggleGroupResult[K]) IsSelected
Section titled “func (ToggleGroupResult[K]) IsSelected”func (r ToggleGroupResult[K]) IsSelected(k K) boolIsSelected reports whether the item with key k is on.
func (ToggleGroupResult[K]) ItemProps
Section titled “func (ToggleGroupResult[K]) ItemProps”func (r ToggleGroupResult[K]) ItemProps(k K) tela.PropItemProps returns the props of the item with key k (React Aria’s useToggleButtonGroupItem): in a single-selection group the “radio” role with aria-checked, in a multiple-selection one the “button” role with aria-pressed (so the aria-pressed: or checked: variants style the items that are on); the ref, the disabled state, a tab stop on the item that had the focus last (else the first one that is on, else the first enabled one), and the handlers: a click, a tap, Enter or Space toggles the item, and the arrow keys of the group’s orientation move the focus without toggling.
Render the item with a Button, or with any element that should behave as one.
func (ToggleGroupResult[K]) ItemRef
Section titled “func (ToggleGroupResult[K]) ItemRef”func (r ToggleGroupResult[K]) ItemRef(k K) tela.ElementRefItemRef returns the ref that ItemProps(k) attaches to the item, to share with another hook on its element.
type ToggleOptions
Section titled “type ToggleOptions”type ToggleOptions struct { // Selected points to the state of a controlled control; nil leaves the // state to the hook. Selected *bool // DefaultSelected is the initial state of an uncontrolled control. DefaultSelected bool // OnChange is called with the next state when the user toggles the // control: for a controlled control it is how the change is asked for, // for an uncontrolled one a notification. OnChange func(selected bool) // OnPress is called when the control is pressed, before the state // changes; the other press callbacks are those of [PressOptions]. OnPress func(PressEvent) // OnPressStart is called when a press begins. OnPressStart func(PressEvent) // OnPressEnd is called when a press ends, whether it counts or not. OnPressEnd func(PressEvent) // OnPressChange is called with true when a press starts and false when // it ends. OnPressChange func(pressed bool) // OnPressUp is called when the pointer or the key is released over the // control. OnPressUp func(PressEvent) // Disabled makes the control inert and disabled for styling and // assistive technologies (h.Disabled). Disabled bool // ReadOnly keeps the control focusable and readable but not // changeable (aria-readonly). ReadOnly bool // ID is the id of the element, for a Label's For; the default is one // made with tela.UseID, returned in the ID field of the result. ID string // Ref is the element's ref, for a hook that is used together with // others on one element (see the package documentation); the zero // value makes the hook create its own. Ref tela.ElementRef}ToggleOptions configures UseToggleButton, UseCheckbox and UseSwitch: a two-state control. Its state is uncontrolled when Selected is nil (the hook keeps it, starting at DefaultSelected) and controlled when Selected points to the caller’s state: the control then shows it and only asks OnChange for the next value, as an Input with Value and OnChange does. OnChange is also called for an uncontrolled control, to follow it.
// Uncontrolled: the hook keeps the state.aria.ToggleOptions{DefaultSelected: true}// Uncontrolled, and told of every change.aria.ToggleOptions{DefaultSelected: true, OnChange: func(on bool) { save(on) }}// Controlled: the caller keeps it.on, setOn := tela.UseState(false)aria.ToggleOptions{Selected: &on, OnChange: setOn.Set}To follow an uncontrolled control, read the Selected field of its result.
Example
// An uncontrolled switch that tells the app about every change, and a// controlled one: Selected points to the app's own state.page := func() tela.Node { saved, setSaved := tela.UseState("none") on, setOn := tela.UseState(true) auto := aria.UseSwitch(aria.ToggleOptions{ DefaultSelected: true, OnChange: func(v bool) { setSaved.Set(fmt.Sprint(v)) }, }) manual := aria.UseSwitch(aria.ToggleOptions{Selected: &on, OnChange: setOn.Set}) return Div( Button(auto.Props, AriaLabel("Auto"), Class("h-6 w-11")), Button(manual.Props, AriaLabel("Manual"), Class("h-6 w-11")), Textf("auto saved: %s, manual: %v", saved, on))}app := telatest.MountFunc(&exampleTB{}, page)app.Click(telatest.ByRole("switch", "Auto"))app.Click(telatest.ByRole("switch", "Manual"))app.ExpectText("auto saved: false, manual: false")fmt.Println("auto saved: false, manual: false")// Output: auto saved: false, manual: falsetype ToggleResult
Section titled “type ToggleResult”type ToggleResult struct { // Props go to the element: the press, the role, the tab stop, the // checked or pressed state, the disabled and read-only states and the // ID. Props tela.Prop // Selected is the current state, controlled or not. Selected bool // Pressed is true while the control is pressed. Pressed bool // ID is the element's id: ToggleOptions.ID, or one made by the hook. // A Label(For(id)) names the control and toggles it when clicked, if // the control is a Button element. ID string // Ref is the ref attached to the element by Props. Ref tela.ElementRef}ToggleResult is what UseToggleButton, UseCheckbox and UseSwitch return.
func UseCheckbox
Section titled “func UseCheckbox”func UseCheckbox(o ToggleOptions) ToggleResultUseCheckbox returns the props of a checkbox (React Aria’s useCheckbox): press (a click or a tap toggles it, and so does Space, but not Enter, as in HTML), the “checkbox” role, aria-checked, a tab stop, the disabled and read-only states and an ID for a Label. The checked: variant, and peer-checked: and group-checked:, match it while it is selected (so do aria-checked: and its peer and group forms).
box := aria.UseCheckbox(aria.ToggleOptions{DefaultSelected: true})return Div(Class("flex items-center gap-2"), Button(box.Props, Class("size-4 rounded border checked:bg-primary")), Label(For(box.ID), Text("Notifications")))Render a checkbox with a Button, so that a Label can name it and toggle it when it is clicked: Tela’s Label does that for an Input, a Textarea or a Button.
Selected, DefaultSelected and OnChange follow ToggleOptions. There is no indeterminate state yet.
Example
t := &exampleT{}defer t.close()page := func() tela.Node { box := aria.UseCheckbox(aria.ToggleOptions{DefaultSelected: true}) return Div(Class("flex items-center gap-2"), Button(box.Props, Class("size-4 rounded border checked:bg-primary")), Label(For(box.ID), Text("Notifications")))}app := telatest.MountFunc(t, page)fmt.Println(app.AriaSnapshot())app.Click(telatest.ByText("Notifications")) // the Label toggles itfmt.Println(app.AriaSnapshot())// Output:// - checkbox "Notifications" [checked]// - text: Notifications// - checkbox "Notifications"// - text: Notificationsfunc UseSwitch
Section titled “func UseSwitch”func UseSwitch(o ToggleOptions) ToggleResultUseSwitch returns the props of a switch (React Aria’s useSwitch): the same as UseCheckbox with the “switch” role.
sw := aria.UseSwitch(aria.ToggleOptions{Selected: &on, OnChange: setOn.Set})return Button(sw.Props, Class("h-6 w-11 rounded-full bg-muted checked:bg-primary"), Span(Class("block size-5 rounded-full bg-white transition checked:translate-x-5")))Example
t := &exampleT{}defer t.close()page := func() tela.Node { on, setOn := tela.UseState(false) sw := aria.UseSwitch(aria.ToggleOptions{Selected: &on, OnChange: setOn.Set}) return Button(sw.Props, AriaLabel("Wi-Fi"), Class("h-6 w-11 rounded-full bg-muted checked:bg-primary"))}app := telatest.MountFunc(t, page)app.Click(telatest.ByRole("switch", "Wi-Fi"))fmt.Println(app.AriaSnapshot())// Output: - switch "Wi-Fi" [checked]func UseToggleButton
Section titled “func UseToggleButton”func UseToggleButton(o ToggleOptions) ToggleResultUseToggleButton returns the props of a button that is on or off (React Aria’s useToggleButton): UseButton plus the state, shown as aria-pressed, so the aria-pressed: variants style it. Enter, Space, a click or a tap toggles it.
bold, setBold := tela.UseState(false)tb := aria.UseToggleButton(aria.ToggleOptions{Selected: &bold, OnChange: setBold.Set})return Button(tb.Props, Class("rounded px-2 aria-pressed:bg-accent"), Text("B"))Example
t := &exampleT{}defer t.close()page := func() tela.Node { bold, setBold := tela.UseState(false) tb := aria.UseToggleButton(aria.ToggleOptions{Selected: &bold, OnChange: setBold.Set}) return Button(tb.Props, Class("rounded px-2 aria-pressed:bg-accent"), Text("Bold"))}app := telatest.MountFunc(t, page)fmt.Println(app.AriaSnapshot())app.Click(telatest.ByRole("button", "Bold"))fmt.Println(app.AriaSnapshot())// Output:// - button "Bold"// - button "Bold" [pressed]type TooltipTriggerOptions
Section titled “type TooltipTriggerOptions”type TooltipTriggerOptions struct { // Delay is how long the pointer must stay on the trigger before the // tooltip opens: [DefaultTooltipDelay] when zero, none with [NoDelay]. // A tooltip opened by the keyboard focus has no delay, and neither // does one that opens while another tooltip is open or closed less than // CloseDelay ago (React Aria's warm-up and cool-down). Delay time.Duration // CloseDelay is how long the tooltip stays after the pointer leaves: // [DefaultTooltipCloseDelay] when zero, none with [NoDelay]. Moving to // the tooltip itself keeps it open. CloseDelay time.Duration // Disabled turns the tooltip off: it does not open. Disabled bool // Trigger is what opens the tooltip: "" for both the pointer hovering // and the keyboard focus (focus that a pointer press did not give), or // "focus" for the keyboard focus alone. Another value panics in dev // mode. Trigger string // Open points to the open state of a controlled tooltip; nil leaves the // state to the hook, starting at DefaultOpen. OnOpenChange is called // with the next state: for a controlled tooltip it is how the change is // asked for, for an uncontrolled one a notification. Open *bool DefaultOpen bool OnOpenChange func(open bool) // Ref is the trigger's ref, for a hook used together with others on // the same element; the zero value makes the hook create its own. Ref tela.ElementRef // TooltipRef is the tooltip element's ref, which the trigger's // aria-describedby points to; the zero value makes the hook create its // own. TooltipRef tela.ElementRef}TooltipTriggerOptions configures UseTooltipTrigger.
type TooltipTriggerResult
Section titled “type TooltipTriggerResult”type TooltipTriggerResult struct { // TriggerProps go to the trigger: the hover, focus, key and pointer // handlers that open and close the tooltip, the ref, and, while the // tooltip is open, aria-describedby. The trigger keeps its own role and // tab stop: tooltips are for elements that are already focusable. TriggerProps tela.Prop // TooltipProps go to the tooltip's element, rendered while Open (in a // Portal, placed by [UseFloating] or [UsePopover]-like options): its // "tooltip" role, the ref, hover that keeps it open, and Escape, which // closes it before anything below it. TooltipProps tela.Prop // Open is whether the tooltip is open. Open bool // Ref is the trigger's ref and TooltipRef the tooltip's. Ref, TooltipRef tela.ElementRef}TooltipTriggerResult is what UseTooltipTrigger returns.
func UseTooltipTrigger
Section titled “func UseTooltipTrigger”func UseTooltipTrigger(o TooltipTriggerOptions) TooltipTriggerResultUseTooltipTrigger returns the props of a tooltip and of its trigger (React Aria’s useTooltipTrigger and useTooltip, with useTooltipTriggerState): it opens when the pointer rests on the trigger or the keyboard focuses it, closes when either leaves, when the trigger is pressed (a pointer, or Enter or Space) and with Escape; other keys leave it open.
tip := aria.UseTooltipTrigger(aria.TooltipTriggerOptions{})fl := aria.UseFloating(tip.Ref, aria.Placement("top"), aria.Offset(6), aria.Flip(), aria.Shift(8))return Fragment( Button(tip.TriggerProps, Class("rounded-md border p-2"), Text("Save")), Portal(Presence(If(tip.Open, Div(tip.TooltipProps, fl.Props, Exit("opacity-0"), Class("rounded bg-foreground px-2 py-1 text-xs text-background transition starting:opacity-0"), Text("Saves the file (Ctrl+S)"))))))The tooltips of a window behave as a group: opening one closes the others, and while one is open, or one closed less than DefaultTooltipCloseDelay ago, the next one opens at once. The delays run on the frame clock, with no goroutine, so an idle window with a tooltip pending or open asks for no frames.
Example
// The tooltip opens 1.5 s after the pointer rests on its trigger, and// closes half a second after it leaves.page := func() tela.Node { tip := aria.UseTooltipTrigger(aria.TooltipTriggerOptions{}) fl := aria.UseFloating(tip.Ref, aria.Placement("top"), aria.Offset(6), aria.Flip(), aria.Shift(8)) return Div(Class("relative h-full"), Button(tip.TriggerProps, Class("absolute left-4 top-[100px] h-8 w-24"), Text("Save")), Portal(If(tip.Open, Div(tip.TooltipProps, fl.Props, Class("rounded bg-black px-2 py-1 text-xs text-white"), Text("Saves the file")))))}app := telatest.MountFunc(&exampleTB{}, page, telatest.Size(400, 300), telatest.NoSettle())count := func() int { return app.Count(telatest.ByRole("tooltip", "Saves the file")) }app.Hover(telatest.ByRole("button", "Save"))app.Advance(time.Second)fmt.Println("after 1 s:", count())app.Advance(600 * time.Millisecond)fmt.Println("after 1.6 s:", count())app.MouseMove(300, 250)app.Advance(600 * time.Millisecond)fmt.Println("0.6 s after leaving:", count())// Output:// after 1 s: 0// after 1.6 s: 1// 0.6 s after leaving: 0