7.6k

Segmented Control

A standalone exclusive-choice segment. Each segmented-control is one named, pressable item: bind selected from the model, dispatch on-press, and add a vector icon when the label benefits from one. Put several in a row for a compact choice control; use tabs when you also need the tab-strip container and conditional panel.

segmented-control
Standalone segmented controls rendered by the engine (light theme)
Day, Week, and Month segments with Week selected

Markup

<row gap="6">
  <segmented-control selected="{range == 'day'}" on-press="choose_day">Day</segmented-control>
  <segmented-control selected="{range == 'week'}" icon="settings" on-press="choose_week">Week</segmented-control>
  <segmented-control selected="{range == 'month'}" on-press="choose_month">Month</segmented-control>
</row>

selected is model-owned. Pressing a segment sends its message; the next view rebuild reflects the new selection. The segment itself is a text-bearing control, so it needs visible content or a label accessible name.

Icons and sizes

The icon attribute uses the same closed vector-icon vocabulary as button. It is drawn as part of the segment's own hit target and follows the segment's state tint. size selects the shared control register; label supplies an accessible name when the visible content is absent.

<row gap="6">
  <segmented-control size="sm" selected="{view == 'list'}" icon="file-text" label="List" on-press="show_list" />
  <segmented-control size="sm" selected="{view == 'board'}" icon="settings" label="Board" on-press="show_board" />
</row>

Programmatic construction (Zig)

In a Zig view, use the same retained widget kind directly with canvas.Ui:

ui.row(.{ .gap = 6 }, .{
    ui.el(.segmented_control, .{ .text = "Day", .selected = model.range == .day, .on_press = .choose_day }, .{}),
    ui.el(.segmented_control, .{ .text = "Week", .icon = "settings", .selected = model.range == .week, .on_press = .choose_week }, .{}),
    ui.el(.segmented_control, .{ .text = "Month", .selected = model.range == .month, .on_press = .choose_month }, .{}),
})

Attributes

AttributeDescription
textText value for text-bearing elements; a literal or one {binding}.
selectedSelected state; often a {a == b} equality.
disabledDisables the control; true/false or a {binding}.
sizeControl size: default|sm|lg|icon. On text, also the typography rungs heading|display (themable token steps above title - a section heading, a hero stat); numeric sizes are not accepted - retheme the typography tokens instead.
iconbutton, toggle-button, list-item, menu-item: vector icon drawn inline (buttons/toggle-buttons before the label, list/menu items as a leading slot): a built-in name (comptime-validated against canvas.icons.known_icon_names, e.g. save, plus, refresh-cw), an app-registered app:<name>, or one {binding} resolving to such a name. Icon-only buttons when the content is empty — add a label. One hit target, one enabled/disabled tint.
icon-placementIcon slot side on label-bearing buttons/toggle-buttons: leading (default) draws the icon before the label, trailing after it — the next-page chevron. Icon-only buttons center the glyph regardless.
labelAccessible name; when set it REPLACES the element's text as the announced name - screen readers and automation snapshots see the label, never the text it shadows.
keySibling-scoped identity key; on for, names an item field.
global-keyParent-independent identity: ids survive reparenting between containers.
on-pressDispatch a Msg on press: tag or tag:{payload}. Legal on any element — a bound press handler makes it pressable, and presses on plain text/icons inside it fall through to it (dragging still selects text).
on-holdPress-and-hold Msg: a pointer held ~350 ms dispatches it and the release then presses nothing; a quick click dispatches on-press as usual. A right/ctrl-click with no context menu on the route dispatches it immediately. Like on-press, binding it makes any element pressable.