Radio Group#

A role="radiogroup" of native <input type="radio"> elements sharing one generated name, under a single string model. The platform provides the roving arrow keys and the one-of-many rule; each item's visible item-control and item-indicator are pure styling surfaces the design system paints.

Import#

TSX
import { RadioGroup } from '@sigx/zero/radio-group';

RadioGroup is a compound: RadioGroup.Root, RadioGroup.Label, RadioGroup.Item. It is also re-exported from the @sigx/zero root, together with radioGroupAnatomy and useRadioGroupContext.

Usage#

TSX
import { component } from 'sigx';
import { RadioGroup } from '@sigx/zero/radio-group';

const Plan = component(({ signal }) => {
    const state = signal({ plan: 'free' });

    return () => (
        <RadioGroup.Root model={() => state.plan} name="plan">
            <RadioGroup.Label>Plan</RadioGroup.Label>
            <RadioGroup.Item value="free">Free</RadioGroup.Item>
            <RadioGroup.Item value="pro">Pro</RadioGroup.Item>
            <RadioGroup.Item value="team" disabled>Team</RadioGroup.Item>
        </RadioGroup.Root>
    );
});

model={() => state.plan} binds the selected value both ways — checking an item writes state.plan, and writing state.plan checks the item. Leave the model off and pass defaultValue to keep the state inside the component; valueChange fires either way. See Models. Each item's default slot is its label text; with no children the item-label part is not rendered.

Horizontal#

TSX
<RadioGroup.Root defaultValue="s" orientation="horizontal">
    <RadioGroup.Item value="s">S</RadioGroup.Item>
    <RadioGroup.Item value="m">M</RadioGroup.Item>
    <RadioGroup.Item value="l">L</RadioGroup.Item>
</RadioGroup.Root>

orientation defaults to vertical and renders as data-orientation on the root, which is where a recipe switches the layout. The arrow keys are the platform's and move in both axes whatever the orientation.

Inside a Field#

TSX
<Field.Root required invalid={!state.shipping}>
    <Field.Label>Shipping</Field.Label>
    <RadioGroup.Root model={() => state.shipping}>
        <RadioGroup.Item value="standard">Standard</RadioGroup.Item>
        <RadioGroup.Item value="express">Express</RadioGroup.Item>
    </RadioGroup.Root>
    <Field.Error>Choose a shipping method.</Field.Error>
</Field.Root>

Inside a Field.Root the group's disabled, invalid and required each derive as the group's own prop or the field's, and the root points its aria-labelledby at the field's label id and its aria-describedby at the description and error. A group has no single control for a <label for> to name, so the field labels the group itself; RadioGroup.Label is the standalone equivalent.

Anatomy#

PartElementStatesFlagsNotes
rootdivdisabled, invalid, requiredrole="radiogroup", data-orientation. Carries the variant axes.
labeldivdisabled, invalid, requiredThe group's caption. Inside root.
itemlabelchecked | uncheckeddisabled, focus-visibleThe label row of one option; the pointer target. Inside root.
item-controlspanchecked | uncheckeddisabled, focus-visible, pressed, press-animatingThe circle a design system paints. Publishes press feedback. Inside item.
item-indicatorspanchecked | uncheckedEmpty; the design system draws the dot. Inside item-control.
item-labelspanchecked | uncheckeddisabledThe option text; rendered only when the item's default slot is given. Inside item.
hidden-inputinputThe native radio, visually hidden and focusable; carries the shared name, the item's value, required and aria-invalid. Inside item.

Every part carries data-scope="radio-group" and data-part="<part>". The root carries data-orientation; orientation is declared on the scope. The hidden-input is a real, styleable part: the native radios render so the group posts pre-hydration, hidden with an inline clip rather than display: none so they stay focusable. See The anatomy contract.

invalid is a fact about the group, not an option: it renders on the root and label only, and no item part declares it — an item cannot be wrong on its own. disabled is both: the group's flag reaches every item, and an item's own disabled applies to just that one. Press feedback is cross-element: a press anywhere in the item's label row, or Space on its hidden input, lands on the item-control, with the --press-* coordinates computed against its rect.

Props#

RadioGroup.Root#

PropTypeDefaultDescription
modelstringTwo-way binding of the selected item's value.
defaultValuestring''Initial value when uncontrolled; the empty string selects nothing.
valueChangeevent (value: string)Fires whenever the selection changes.
namestringgeneratedThe shared form name on every hidden input; an SSR-safe id when unset.
requiredbooleanfalseRenders required on every hidden input and data-required on root and label; a wrapping Field's required also applies.
invalidbooleanfalseRenders aria-invalid on every hidden input and data-invalid on root and label; a wrapping Field's invalid also applies.
disabledbooleanfalseDisables every item; renders data-disabled on root, label and each item; a wrapping Field's disabled also applies.
orientation'horizontal' | 'vertical''vertical'Layout direction; rendered as data-orientation.
color / size / variant / axes / modsdesign-system vocabularyThe variant axes, rendered as data-* on root.
classstringExtra classes on the root element.

RadioGroup.Label#

Only class. Renders the label part around its children, mirroring the group's flags.

RadioGroup.Item#

PropTypeDefaultDescription
valuestringrequiredThe value this item selects.
disabledbooleanfalseDisables this item alone; renders data-disabled on item, item-control and item-label.
classstringExtra classes on the item element.

Keyboard#

KeyAction
ArrowDown / ArrowRightMove focus to the next enabled radio and select it, wrapping at the end.
ArrowUp / ArrowLeftMove focus to the previous enabled radio and select it, wrapping at the start.
SpaceSelect the focused radio if it is not yet selected.
TabEnters the group on the selected radio (or the first enabled one when none is selected) and leaves it on the next press.

The roving comes from the platform: same-name native radios are one tab stop, so zero adds no key handling of its own beyond press feedback.

In the shipped design systems#

Both @sigx/zero-basic and @sigx/zero-daisyui wire color (the eight recommended roles) and size (xsxl) on radio-group, so <RadioGroup.Root color="secondary" size="sm"> 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.

The dot is the design system's: a recipe draws it on the item-indicator from [data-state="checked"] and lays the items out from the root's data-orientation.

Checkbox · Switch · Select · Field