Skip to main content
A SurfaceNode is one piece of the tree:
  • kind: container or leaf
  • layout: LayoutSpec
  • content: what to draw (leaf only)
  • children: child nodes (containers)
  • id: SurfaceId
  • hit / hit_data: pointer routing
  • semantics: optional accessibility metadata
  • focus_scope: keep keyboard focus inside this subtree
Leaf nodes draw one control. Set hit (or use a constructor that sets it) on anything you want to click.

Constructors vs manual nodes

Many controls have helpers on SurfaceNode:
ui.SurfaceNode.button(id, "Save")
ui.SurfaceNode.label(id, "Name", 14, null)
ui.SurfaceNode.toggle(id, .{ .checked = true })
For section, tabs, form_row, color_swatch, and gradient_swatch, build the struct manually:
ui.SurfaceNode{
    .id = id,
    .kind = .leaf,
    .layout = .{ .width = .fill },
    .content = .{ .form_row = .{
        .label = "Refresh interval",
        .value = "3 seconds",
    } },
}

Structural leaves

panel (PanelSpec)

Rounded card background with optional border and shadow.
FieldPurpose
backgroundFill color (required)
border, shadow, highlight_edgeChrome
radius, border_widthCorner and stroke
ui.SurfaceNode.panel(id, .{ .width = .fill, .height = .fill }, .{
    .background = theme.surfaces.raised,
    .radius = theme.density.panel_radius,
})

rect (RectStyle)

Flat filled or bordered rectangle. Use for dividers, highlights, custom backgrounds.
FieldPurpose
fillBackground color
border, border_width, radius, cornersOptional outline

spacer (container kind)

Empty node. Size comes from LayoutSpec (often .fill to push siblings).

Text and actions

label (LabelSpec)

FieldDefaultNotes
textrequiredUTF-8
font_size14
colortheme foregroundnull = theme default
baseline_offsetautoFine-tune vertical alignment
No hit region. Not focusable.

button (ButtonSpec)

FieldDefaultNotes
textrequired
font_size14
hovered, pressed, disabledfalseDrive from input.hovered / input.active
background, hover_background, pressed_backgroundthemeOptional overrides
text_color, border, radius, paddingtheme defaults
Constructor sets hit = .button.
ui.SurfaceNode.button(save_id, "Save") // minimal
// or full spec:
ui.SurfaceNode{
    .id = save_id,
    .kind = .leaf,
    .content = .{ .button = .{
        .text = "Save",
        .hovered = ui_state.input.hovered.eql(save_id),
        .pressed = ui_state.input.active.eql(save_id),
    } },
    .hit = .button,
}

icon_button (IconButtonSpec)

FieldPurpose
icon?*const anyopaque image pointer
fallback_textShown when icon is null
icon_size, font_sizeLayout
selected, disabledState
background, selected_background, icon_color, border, radiusColors
hit = .icon_button.

Text input

text_input (TextInputSpec)

FieldPurpose
textCommitted UTF-8 from your InputBuffer
preeditIME composition string
cursor_byte, selection_start_byte, selection_end_byteCaret and selection
placeholderShown when text is empty
focusedFocus ring and cursor
disabled, wrapState
background, text_color, placeholder_color, border, cursor, selectionColors
padding, radiusChrome
hit = .text_input. Pair with ui.InputBuffer(N) in app state and Wayland text-input protocol for IME.
ui.SurfaceNode.textInput(field_id, .{
    .text = self.search.text(),
    .placeholder = "Search processes",
    .focused = self.search_focused,
    .cursor_byte = self.search.cursorByte(),
})

Boolean and numeric controls

checkbox (CheckboxSpec)

FieldPurpose
labelOptional text beside box
checked, disabledState
font_size, box_size, colorLayout and colors
hit = .checkbox.

toggle (ToggleSpec)

FieldDefault
checked, disabledfalse
width38
height22
colortheme accent
hit = .toggle.

slider (SliderSpec)

FieldPurpose
value_q160..65535 (0%..100% in 16.16 fixed point)
disabledGray out
height, track_height, colorLayout
hit = .slider. Default layout width is .fill.

number_input (NumberInputSpec)

Stepper with decrement/value/increment zones.
FieldPurpose
value_textDisplay string
font_size, height, min_widthLayout
disabledState
background, button_background, text_color, border, radiusColors
hit = .number_value on the center field. Decrement/increment buttons use separate hit kinds (.number_decrement, .number_increment) when emitted.

progress (ProgressSpec)

Read-only bar.
FieldPurpose
value_q16Fill amount
height, colorLayout
No hit region.

Selection and color

select (SelectSpec)

Dropdown trigger or inline popup.
FieldPurpose
selected_text, placeholderDisplay
optionsSlice of option labels
selected_index, hover_index, scroll_offsetList state
openPopup visible
mode.trigger, .inline_popup, or .menu
item_height, max_visible, font_sizeLayout
disabledState
hit = .select. Option rows use .select_option in overlay pass.

color_picker (ColorPickerSpec)

Inline SV square + hue/alpha bars.
FieldPurpose
colorCurrent Color
alpha0..255
sv_size, bar_width, paddingLayout
disabled, background, border, radiusState and chrome
hit = .color_picker_sv on the saturation/value area. Hue and alpha bars have .color_picker_hue and .color_picker_alpha.

color_picker_popup (ColorPickerPopupSpec)

Larger popup variant used by settings. Pass hsv from ui.drawing.HSV.

color_swatch / gradient_swatch

Small preview tiles. Set layout to fixed size. gradient_swatch takes start, mid, end colors.

tabs (TabsSpec)

FieldPurpose
itemsSlice of { .id, .label, .disabled }
activeSelected index
font_size, height, horizontal_padding, gapLayout
Color fieldsActive/inactive tab styling
Tab hits use .tab. Build manually:
.content = .{ .tabs = .{
    .items = &tab_items,
    .active = self.active_tab,
} },

section (SectionSpec)

Section header with optional underline.
FieldPurpose
titleHeader text
font_size, color, underline, show_underlineStyle

form_row (FormRowSpec)

Settings-style label + value row.
FieldPurpose
label, valueText
label_width, label_font_size, truncate_labelLayout
label_color, value_colorColors

list_row (ListRowSpec)

Selectable list item with optional icon and subtitle.
FieldPurpose
title, subtitleText
icon, icon_sizeLeading image
selectedHighlight state
Color and padding, radius, font_sizeChrome
Constructor: listRow(id, spec, hit_data). hit = .button, hit_data carries list index.

scrollbar (ScrollbarSpec)

FieldPurpose
stateui.ScrollState (offset, content height, viewport)
width, disabledLayout
track_color, thumb_color, radiusColors
hit = .scroll. Pair with app scroll offset updates on .scroll dispatch events.

Media

image (ImageSpec)

FieldPurpose
image?*const otter_render.Image
fit.contain, .cover, .cover_scaled, .stretch, .center
source_rect, alpha, pixelated, flip_x, radius, cornersRendering

icon (IconSpec)

FieldPurpose
imageOptional bitmap
fallback_textLetter or symbol if no image
fallback_font, font_size, colorText fallback styling
fill, circle_fillBackground behind glyph

Hit kinds reference

HitKindControl
.buttonbutton, list_row
.text_inputtext_input
.checkboxcheckbox
.toggletoggle
.sliderslider
.icon_buttonicon_button
.number_decrement / .number_value / .number_incrementnumber_input
.select / .select_optionselect
.color_picker_sv / .hue / .alphacolor_picker
.tabtabs
.scrollscrollbar
.overlayQueued overlays
.genericCustom; set manually
Set hit_data: u64 to pass a list index, enum tag, or pointer cookie to your handler.

Disabled content

ButtonSpec.disabled, ToggleSpec.disabled, SelectSpec.disabled, and similar flags mark the control visually disabled and set HitFlags.enabled = false so hitTest skips them.

Next