Button Toggle
A segmented single-select — one-of-N choices rendered as a row of connected buttons. Form-associated, holds the selected value, and follows the WAI-ARIA radiogroup pattern with arrow-key navigation.
Preview
<esa-button-toggle label="View" id="bt-preview"></esa-button-toggle>
<script>
el.options = [
{ value: 'list', label: 'List' },
{ value: 'grid', label: 'Grid' },
{ value: 'map', label: 'Map' },
];
el.value = 'grid';
</script> Sizes
Four sizes share the form scale. Default is md.
<esa-button-toggle size="xs"></esa-button-toggle>
<esa-button-toggle size="sm"></esa-button-toggle>
<esa-button-toggle size="md"></esa-button-toggle>
<esa-button-toggle size="lg"></esa-button-toggle> Icons
Each option may carry an icon — inner Lucide SVG markup (the same paths convention as esa-icon). It renders before the label and inherits currentColor, so it flips to the inverse color on the selected segment.
el.options = [
{ value: 'grid', label: 'Grid', icon: "<rect width='18' height='18' x='3' y='3' rx='2'/>…" },
{ value: 'kanban', label: 'Kanban', icon: "<path d='M3 3h18v18H3z'/>…" },
{ value: 'timeline', label: 'Timeline', icon: "<path d='M8 2v4'/>…" },
]; Omit label for an icon-only segment — set ariaLabel so it still has an accessible name.
el.options = [
{ value: 'grid', icon: "<rect …/>", ariaLabel: 'Grid' },
{ value: 'kanban', icon: "<path …/>", ariaLabel: 'Kanban' },
{ value: 'timeline', icon: "<path …/>", ariaLabel: 'Timeline' },
]; States
<esa-button-toggle label="Default"></esa-button-toggle>
<esa-button-toggle label="With hint" hint="Pick a layout"></esa-button-toggle>
<esa-button-toggle label="Disabled" disabled></esa-button-toggle> Form participation
The control submits its selected value under its name. Submitting logs the view entry.
<form>
<esa-button-toggle name="view" label="View"></esa-button-toggle>
<button type="submit">Submit</button>
</form> API
| Prop | Type | Default | Description |
|---|---|---|---|
options | { value, label, icon?, ariaLabel? }[] | [] | The segments. Set as a property (an array, not an attribute). Optional icon is inner Lucide SVG markup (the same paths convention as esa-icon), rendered before the label and inheriting currentColor. Omit label for an icon-only segment, but then set ariaLabel for its accessible name. |
value | string | '' | Selected option's value. Single-select; bound to the form value. |
label | string | '' | Optional visible label above the group; becomes the radiogroup name. |
hint | string | '' | Optional helper text below the group. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'md' | Control size (shared form scale). |
required | boolean | false | Marks the field required (adds an asterisk and aria-required). |
disabled | boolean | false | Disables every segment. |
Events
| Event | Type | Default | Description |
|---|---|---|---|
change | CustomEvent<{ value: string }> | — | Fired when the selection changes. Composed and bubbling; detail.value is the newly selected value. |
Accessibility
- Renders
role="radiogroup"wrappingrole="radio"buttons witharia-checkedreflecting selection. - Roving tabindex: only the selected (or first) segment is tab-reachable; Arrow keys move selection through the group, wrapping at the ends.
HomeandEndjump to the first and last segments; Enter and Space select the focused segment.- The
labelbecomes the group's accessible name viaaria-labelledby;hintis wired witharia-describedby. - Visible focus ring on the active segment via
--focus-ring-*tokens; the focused segment is raised so its ring sits above neighbors. - Best for small fixed option sets — past about six options, prefer a select.