ToggleGroup
A mutually exclusive button group for single/multiple selection: single mode uses radio semantics, multiple mode uses checkbox semantics; keyboard arrow and Home/End navigation and controlled value. Supports size steps, vertical layout, attached form, item icons, mandatory selection, multiple-select limit, spread, selection color and validation status.
ToggleGroup vs ButtonGroup
- oas-toggle-group (this component): state selection — a controlled set of selected states via
value, radio/checkbox semantics (aria-checked), single/multiple modes, roving keyboard, and form capabilities such as mandatory / max-count / status. Suited to view switching, filter conditions and rich-text style toggles. - oas-button-group: action orchestration — buttons each trigger their own action (
oas-click) with no selected-state semantics; it also keeps avalue/multipleselection form for compatibility, and new code for state-selection scenarios is recommended to use toggle-group (fuller ARIA semantics and keyboard model).
Single Select
Without multiple, it is single-select (role="radiogroup" + radio); value is a string, and only one item is pressed at a time.
Single select
Multiple Select
With multiple, it becomes multi-select (role="group" + checkbox); value is a JSON array string.
Multiple select
Disabled
Item-level disabled: true disables that item; disabled items are skipped in keyboard navigation.
Disabled items
Controlled Selection (value)
value is a controlled channel: a string for single select, a JSON array string for multiple; externally setting the attribute immediately reflects in the selected state (clicks inside the group also write back the attribute). The buttons below drive the selection externally:
Controlled value
Presetting value="week" / value='["bold"]' gives both groups an initial selected state; writing the value attribute externally switches the selected state immediately.
Declarative Child-Element Channel
Besides the items JSON, you can declare options declaratively with <oas-toggle-item> child elements (the items attribute takes precedence when explicitly set; otherwise child elements are parsed and converge into the same rendering path). The default slot text is the label; attributes map to the ToggleItem fields: value / disabled / icon / aria-label. Child additions, removals, attribute and text changes re-render automatically (MutationObserver); single/multiple (multiple) semantics are identical to the items channel.
Declarative child elements (single + multiple)
Size Steps (size)
size has three steps: small / medium (default) / large; control height and font size follow global tokens. When not set explicitly, the config-provider size injection is respected (global density). Use small inside tables and large on filter bars:
Size steps
Item Icons (icon / icon-only)
Add an icon field to an items entry (or the icon attribute on a child element) to render an icon before the text (an @oas-ui/icons registry name). An icon item without a label automatically becomes icon-only (square), with the icon name as the fallback accessible name — prefer providing a precise name via the ariaLabel field; labeled items can also override the accessible name with ariaLabel (e.g. "B" read as "Bold").
A rich-text toolbar is the classic scenario — text styles as multiple, alignment as single:
Rich-text toolbar (multiple + single + icon-only)
Vertical Layout (vertical)
The vertical boolean switches to a vertical group (syncing aria-orientation="vertical"); combined with icon-only items it is the classic view-switching form:
Vertical view switching (vertical + icon-only single)
The right group shows the attached vertical form. Long-option vertical groups with labels work the same way: vertical plus plain items.
Attached Form (attached)
The default is detached (a gap between buttons, each with its own radius — unchanged behavior); the attached boolean enables attached — zero gap, adjacent borders merged to 1px, and only the group ends keep rounded corners (left/right in horizontal, top/bottom in vertical; logical properties adapt to RTL). Hover / focus / selected items automatically raise above neighbors so their borders are not clipped:
Detached (default) vs attached
Group Variant (variant)
The variant group-level variant passes through to every toggle item (aligned with the oas-button variant system): solid (default, bordered box, selected = solid fill) / outlined (transparent background with border; selected = primary border + faint tint) / filled (soft tinted background, no border; selected = solid fill) / text (no border or background; selected = faint tint + primary text). Invalid values silently fall back to solid; orthogonal to attached / vertical / color / status (status colors win). View switches and rich-text style groups suit attached + solid; filter panels suit filled or outlined:
Four variants (selected-state comparison)
Spread (spread)
The spread boolean makes the group fill the parent width with equal-width items (mobile action bars / filter bars); vertical groups also fill the width:
Mobile action bar (spread)
Mandatory Selection (mandatory)
mandatory(boolean): the last selected item in multiple mode cannot be deselected (click/keyboard intercepted, no event dispatched); single mode is radio semantics and naturally never empty.mandatory="force": implies the interception above, and auto-selects the first non-disabled item when empty — no initial value, value cleared, value pointing to a missing item, or options changing so the value dangles all trigger the fallback. Wizard flows (day/week/month must have one) need zero host code:
Mandatory (force wizard flow + multiple mandatory)
Note: the force fallback only writes the value attribute and does not dispatch oas-change (not a user interaction; the value attribute is the controlled source of truth) — hosts can listen for attribute changes or read value.
Multiple-Select Limit (max-count)
max-count (effective only with multiple) caps the number of selected items: once the cap is reached, unselected items are greyed out (same visuals as disabled, aria-disabled synced), clicks/keyboard are intercepted and oas-exceed-limit is dispatched (detail: { value, max }); selected items can still be deselected, after which greyed items become selectable again. Invalid values / 0 / negatives mean no cap; single mode is unaffected:
Multiple-select limit (max-count + oas-exceed-limit)
Selection Color (color)
color follows the unified ui-spec protocol: any CSS color value (#0e7490 / rgb(...), text color auto black/white by luminance) takes precedence; the 11 preset names (magenta / red / volcano / orange / gold / lime / green / cyan / blue / geekblue / purple) resolve to --oas-preset-* tokens (light/dark adaptive); default is the primary color. Theme-level batch customization goes through the CSS variables --oas-toggle-color / --oas-toggle-on-color:
Selection color (color)
Form Validation (status)
status has three states: success / warning / error: unselected item borders take the semantic color, selected items are fully tinted, and the focus ring is dyed to match; error syncs the host aria-invalid (a host-set aria-invalid="true" is equivalent to the error visuals). Submit-validation scenario:
Form validation (status)
Events
Clicking or keyboard toggling dispatches oas-change; single select: detail: { value: string }, multiple select: detail: { value: string[] }.
Change events
API
oas-toggle-group
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
aria-label | Group accessible name (host override; falls back to locale default) | — | — |
attached | Attached form: adjacent items share borders, end radii only (default detached) | — | — |
color | Selected color: preset name or any CSS color (auto text color) | string | — |
disabled | Disables the whole toggle group (explicitly, or inherited from the config-provider global disabled injection) | boolean | — |
items | Options JSON (property assignment reflects to attribute) | ToggleItem[] | string | [] |
mandatory | Mandatory selection: boolean = the sole selected item cannot be deselected; force = auto-select the first enabled item when empty (wizard flow, zero host code) | — | — |
max-count | Multi-select limit: at the limit unselected items grey out and oas-exceed-limit fires; selected items stay removable | string | — |
multiple | Multiple mode (checkbox semantics) | boolean | — |
size | Size preset small / medium (default) / large | string | medium |
spread | Full-width equal split (aligned with button-group spread) | — | — |
status | Validation status: error / warning / success (unselected border + selected block tint) | string | — |
value | Current value: string for single; JSON array string for multiple | string | string[] | [] |
variant | Group-level variant passthrough to items (aligned with the button variant system): solid (default, bordered box, selected = solid fill) / outlined (transparent bg + border; selected = colored border + faint tint) / filled (soft tinted bg, no border; selected = solid fill) / text (no border/background; selected = faint tint + colored text); invalid values silently fall back to solid; orthogonal to attached/vertical | string | — |
vertical | Vertical arrangement (mirrors aria-orientation and axis keys) | boolean | — |
Property (JS property; see each row for attribute reflection)
| Property | Description | Type | Default |
|---|---|---|---|
value | Current value: the selected value string, or an array of selected values when multiple | string | string[] | — |
Events
| Event | Description |
|---|---|
oas-change | Toggle, detail: { value: string | string[] } |
oas-exceed-limit | Selection attempt past max-count, detail: { value, max } |
CSS Variables
| CSS Variable | Default |
|---|---|
--oas-toggle-color | var(--oas-color-primary) |
--oas-toggle-on-color | var(--oas-color-text-on-primary) |
--oas-toggle-tint-text | var(--oas-color-primary-text) |
oas-toggle-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
aria-label | Item accessible name (required semantics for icon-only items; derived from the icon name by default) | — | — |
disabled | Disable this item (not clickable; skipped by arrow keys) | — | — |
icon | Icon (oas-icon name); label optional (icon-only items) | — | — |
value | Item value (data-carrier field of the declarative child-element channel) | — | — |
Slots
| Name | Description |
|---|---|
| default | Button label (default slot text) |
ToggleItem fields:
| Field | Description | Type |
|---|---|---|
label | Button label (omittable for icon-only items) | string |
value | Value (returned with events) | string |
disabled | Disable this item | boolean |
icon | Leading icon (@oas-ui/icons registry name); without a label the item becomes icon-only | string |
ariaLabel | Item-level accessible-name override (fallback: label text; icon name for icon-only items) | string |
Keyboard: in single mode arrow keys move and select (radio group convention); in multiple mode arrow keys move focus (roving tabindex) and Space/Enter toggle; Home/End jump to the first/last enabled item (disabled items skipped). The container has role="radiogroup" / role="group" + aria-label (a host aria-label attribute overrides the locale fallback); selected items expose aria-checked; vertical syncs aria-orientation="vertical".