Checkbox#

A checkbox over a visually hidden native <input type="checkbox">. The native input gives form participation, labelling and the platform's keyboard behavior; the visible control and indicator are pure styling surfaces the design system paints. The boolean model carries checked; indeterminate is a separate visual state on top of it.

Import#

TSX
import { Checkbox } from '@sigx/zero/checkbox';

Checkbox is a compound with a single member, Checkbox.Root. It is also re-exported from the @sigx/zero root, together with checkboxAnatomy.

Usage#

TSX
import { component } from 'sigx';
import { Checkbox } from '@sigx/zero/checkbox';

const Terms = component(({ signal }) => {
    const state = signal({ agreed: false });

    return () => (
        <Checkbox.Root model={() => state.agreed} name="terms" required color="primary">
            I accept the terms
        </Checkbox.Root>
    );
});

model={() => state.agreed} binds the checked state both ways — toggling writes state.agreed, and writing state.agreed toggles the box. Leave the model off and pass defaultChecked to keep the state inside the component; checkedChange fires either way. See Models. The default slot is the label text; with no children the label part is not rendered.

Indeterminate#

TSX
<Checkbox.Root
    model={() => state.all}
    indeterminate={state.some && !state.all}
>
    Select all
</Checkbox.Root>

indeterminate sets the native input's indeterminate property and puts every part in data-state="indeterminate", regardless of the model. The model stays a boolean: a click on an indeterminate box writes the native toggle into the model, and the app decides when indeterminate turns off.

Inside a Field#

TSX
<Field.Root invalid={!!state.error}>
    <Field.Label>Send me the monthly digest</Field.Label>
    <Checkbox.Root model={() => state.subscribe} />
    <Field.Description>We never spam.</Field.Description>
    <Field.Error>{state.error}</Field.Error>
</Field.Root>

Inside a Field.Root the hidden input adopts the field's control id, its disabled / invalid / required flags and its aria-describedby. A prop set on the checkbox wins; the field supplies the rest. The checkbox's own <label> and Field.Label both name the same input and the accessible name concatenates them, so name it in one place — here the Field.Label, with no children on the checkbox.

hideLabel keeps the checkbox's own label part as its accessible name while taking it off screen (data-visually-hidden), for a row where something else carries the visible text:

TSX
<Checkbox.Root model={() => state.selected} hideLabel>Select row {row.name}</Checkbox.Root>

Forwarded attributes split: aria-* lands on the input assistive technology reads (an app aria-describedby joins the Field's), and id, title and data-* on the row. role is refused — the input is the checkbox.

Anatomy#

PartElementStatesFlagsNotes
rootlabelchecked | unchecked | indeterminatedisabled, focus-visible, invalid, requiredThe label row; the pointer target. Carries the variant axes.
hidden-inputinput——The native checkbox, visually hidden and focusable; carries name, value, required, aria-invalid and aria-describedby. Inside root.
controlspanchecked | unchecked | indeterminatedisabled, focus-visible, invalid, pressed, press-animatingThe box a design system paints. Publishes press feedback. Inside root.
indicatorspanchecked | unchecked | indeterminate—Empty; the design system draws the mark. Inside control.
labelspanchecked | unchecked | indeterminatedisabledThe label text; rendered only when the default slot is given. data-visually-hidden under hideLabel. Inside root.

Every part carries data-scope="checkbox" and data-part="<part>". The hidden-input is a real, styleable part: the native input renders so the box posts pre-hydration, and it is hidden with an inline clip rather than display: none so it stays focusable — a design system need not hide it again. See The anatomy contract.

The control carries the invalid flag as well as the root, because the box is what a design system paints and reaching it from the root would cost every recipe a descendant selector. Press feedback is cross-element: a press anywhere in the label row, or Space on the hidden input, lands on the control — the --press-x / --press-y coordinates are computed against the control's rect. The indicator renders empty, so recipes key on [data-state="checked"] and [data-state="indeterminate"] to draw the tick and the dash.

Props#

Checkbox.Root#

PropTypeDefaultDescription
modelboolean—Two-way binding of the checked state.
defaultCheckedbooleanfalseInitial state when uncontrolled.
checkedChangeevent (checked: boolean)—Fires whenever the checked state changes.
indeterminatebooleanfalseSets the native indeterminate property and renders data-state="indeterminate".
namestring—Form field name on the hidden input.
valuestring'on'The value the hidden input posts while checked.
requiredbooleanfalseRenders required and data-required; a wrapping Field's required also applies.
invalidbooleanfalseRenders aria-invalid and data-invalid on root and control; a wrapping Field's invalid also applies.
disabledbooleanfalseRenders disabled and data-disabled; a wrapping Field's disabled also applies.
color / size / variant / axes / modsdesign-system vocabulary—The variant axes, rendered as data-* on root.
hideLabelbooleanfalseKeep the label part as the accessible name, off screen.
classstring—Extra classes on the root element.
aria-* / data-* / id / titleHTML attributes—aria-* on the input; the rest on the row. role is refused.

Keyboard#

KeyAction
SpaceToggles, via the native input; the press lands on the control.
TabMoves focus to and from the hidden input; data-focus-visible on root and control while keyboard-focused.

The platform owns the toggle; zero only mirrors the native change event into the model.

In the shipped design systems#

Both @sigx/zero-basic and @sigx/zero-daisyui wire color (the eight recommended roles) and size (xs–xl) on checkbox, so <Checkbox.Root color="success" 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.

The mark is the design system's: a recipe draws the tick and the indeterminate dash on the indicator from its data-state, and the box's fill on the control from the color axis on the root.

Switch · Radio Group · Field · Toggle