Toggle Group#

A set of two-state buttons under one value model — text alignment, a view switcher, a filter chip row. The root is a role="group" and every item a native <button> with aria-pressed. The model is always string[]: multiple changes how a click writes it, never its shape, so switching a group from single to multiple selection is not a type migration.

Import#

TSX
import { ToggleGroup } from '@sigx/zero/toggle-group';

ToggleGroup is a compound: ToggleGroup.Root, ToggleGroup.Item. It is also re-exported from the @sigx/zero root, together with toggleGroupAnatomy and useToggleGroupContext.

Usage#

TSX
import { component } from 'sigx';
import { ToggleGroup } from '@sigx/zero/toggle-group';

const Alignment = component(({ signal }) => {
    const state = signal({ align: ['left'] });

    return () => (
        <ToggleGroup.Root model={() => state.align} label="Text alignment">
            <ToggleGroup.Item value="left">Left</ToggleGroup.Item>
            <ToggleGroup.Item value="center">Center</ToggleGroup.Item>
            <ToggleGroup.Item value="right">Right</ToggleGroup.Item>
            <ToggleGroup.Item value="justify" disabled>Justify</ToggleGroup.Item>
        </ToggleGroup.Root>
    );
});

model={() => state.align} binds the array of pressed values both ways. Leave the model off and pass defaultValue to keep the state inside the component; valueChange fires either way. See Models. label names the group (aria-label) — a row of "Left / Center / Right" buttons means nothing to a screen reader without it.

Single selection#

Without multiple at most one value is in the array: pressing an item replaces the array with [value], and pressing the item that is already on empties it. Pass deselectable={false} to keep one item always on, the way a radio row behaves.

TSX
<ToggleGroup.Root defaultValue={['grid']} deselectable={false} label="View">
    <ToggleGroup.Item value="grid">Grid</ToggleGroup.Item>
    <ToggleGroup.Item value="list">List</ToggleGroup.Item>
</ToggleGroup.Root>

Multiple selection#

TSX
<ToggleGroup.Root model={() => state.styles} multiple label="Text style">
    <ToggleGroup.Item value="bold">B</ToggleGroup.Item>
    <ToggleGroup.Item value="italic">I</ToggleGroup.Item>
    <ToggleGroup.Item value="underline">U</ToggleGroup.Item>
</ToggleGroup.Root>

Under multiple a press appends the value or filters it out; every item is independent.

Vertical groups#

TSX
<ToggleGroup.Root defaultValue={[]} orientation="vertical" label="Sidebar">

orientation renders as data-orientation on the root and on every item — the divider between items is directional CSS on the item, and a sibling selector cannot see the root — and switches the roving keys.

Rendering an item as your own element#

TSX
<ToggleGroup.Item value="docs" asChild>
    {(p) => <span {...p}>Docs</span>}
</ToggleGroup.Item>

With asChild the default slot receives the part's attribute bag; spread it so the anatomy, aria-pressed, the roving tabIndex and the handlers land on your element. The button contract is supplied by hand — role="button", aria-disabled when disabled, and Enter / Space activation where the platform does not synthesise a click.

Anatomy#

PartElementStatesFlagsNotes
rootdivdisabledrole="group", aria-label from label, data-orientation. Carries the variant axes.
itembuttonon | offdisabled, selected, focus-visible, pressed, press-animatingtype="button", aria-pressed, data-orientation, roving tabIndex. Inside root. asChild.

Every part carries data-scope="toggle-group". An item mirrors the standalone Toggle's on / off contract so a design system can share fill styles between the two, and doubles the on-state as a data-selected presence flag — [data-part="item"][data-selected] composes with other flags where a data-state match cannot. disabled on the root disables every item; each item renders its own data-disabled. Items publish press feedback. See The anatomy contract.

Props#

ToggleGroup.Root#

PropTypeDefaultDescription
modelstring[]Two-way binding of the pressed values, in both selection modes.
defaultValuestring[][]Initial value when uncontrolled.
valueChangeevent (value: string[])Fires whenever the selection changes.
multiplebooleanfalseAllow more than one item on at a time; a press appends or removes instead of replacing.
deselectablebooleantrueIn single mode, pressing the on item turns it off.
loopbooleantrueArrow keys wrap from the last item to the first.
labelstringAccessible name of the role="group" container, rendered as aria-label.
orientation'horizontal' | 'vertical''horizontal'Roving-key axis; rendered as data-orientation on root and items.
disabledbooleanfalseDisables every item; renders data-disabled on the root.
color / size / variant / axes / modsdesign-system vocabularyThe variant axes, rendered as data-* on root.
classstringExtra classes on the root element.

ToggleGroup.Item#

PropTypeDefaultDescription
valuestringrequiredThe value this item toggles in the model.
disabledbooleanfalseSkipped by roving focus; renders data-disabled (and aria-disabled under asChild).
asChildbooleanfalseRender through the default slot, which receives the part bag.
classstringExtra classes.

Keyboard#

KeyAction
ArrowRight / ArrowLeft (horizontal), ArrowDown / ArrowUp (vertical)Move focus to the next / previous enabled item without changing the selection. Mirrored under RTL.
Home / EndFirst / last enabled item.
Enter / Space / clickToggle the focused item.
TabLeaves the group.

Roving focus is pure navigation here — a toggle activates on click, Space or Enter, never on focus. One item is in the tab order: the first enabled item that is on, else the first enabled item. See Behaviors.

In the shipped design systems#

Both @sigx/zero-basic and @sigx/zero-daisyui wire color (all eight recommended roles, defaulting to primary) and size (xsxl) on toggle-group, so <ToggleGroup.Root color="secondary" size="lg"> is styled in both. Neither wires a variant or any mods on the scope; under a design system's /register import those props are therefore absent. See Typed vocabulary.

Toggle for one standalone two-state button · RadioGroup for a form-participating exclusive choice · Tabs when the selection switches a panel.