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>Newsletter</Field.Label>
    <Checkbox.Root model={() => state.subscribe}>Send me the monthly digest</Checkbox.Root>
    <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.

Anatomy#

PartElementStatesFlagsNotes
rootlabelchecked | unchecked | indeterminatedisabled, focus-visible, invalid, requiredThe label row; the pointer target. Carries the variant axes.
hidden-inputinputThe 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 | indeterminateEmpty; the design system draws the mark. Inside control.
labelspanchecked | unchecked | indeterminatedisabledThe label text; rendered only when the default slot is given. 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
modelbooleanTwo-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".
namestringForm 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 vocabularyThe variant axes, rendered as data-* on root.
classstringExtra classes on the root element.

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 (xsxl) 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