Skip to main content

Checkbox

A compound checkbox — a Root that owns the checked state, wrapping an Input and a Label.

Installation

npm install @sipe-team/checkbox
import '@sipe-team/checkbox/styles.css';

Usage

Root generates one id and shares it, so Input and Label are wired together automatically — clicking the label toggles the box.

Examples

Uncontrolled

Give Root a defaultChecked and let it track the state itself — the common case for forms that read the value on submit.

Controlled

Own the state yourself with checked and onCheckedChange when another part of the UI has to react to it. onCheckedChange receives the next boolean.

Sizes

medium (default) fits most forms; small for dense rows, large for touch targets. The box, font, and spacing all scale together.

Indeterminate

indeterminate renders the mixed mark and sets aria-checked="mixed" — use it for a "select all" box whose children are only partly selected.

Disabled

disabled forwards the native attribute to the Input and dims the Label. It applies whether the box is checked or not.

Anatomy

import { Checkbox } from '@sipe-team/checkbox';

export default () => (
<Checkbox.Root defaultChecked>
<Checkbox.Input />
<Checkbox.Label>Label</Checkbox.Label>
</Checkbox.Root>
);

Root renders a <div> and provides context; Input and Label read from it and throw when rendered outside a Root. State (checked, size, disabled, indeterminate) lives on Root, not on the individual parts.

API Reference

Checkbox.Root

Renders a <div> wrapper and forwards its ref to the underlying <input>.

PropTypeDefaultDescription
childrenReactNodeRequired. Usually a Checkbox.Input and a Checkbox.Label.
size'small' | 'medium' | 'large''medium'Scales the box, font size, and spacing.
checkedbooleanControlled checked state. Pair with onCheckedChange.
defaultCheckedbooleanfalseInitial checked state when uncontrolled.
onCheckedChange(checked: boolean) => voidCalled with the next checked value on toggle.
indeterminatebooleanfalseRenders the mixed mark and sets aria-checked="mixed".
disabledbooleanDisables the input and dims the label.
classNamestringAppended to the container class.
styleCSSPropertiesApplied to the container <div>.

Also accepts most of ComponentProps<'input'> (id, name, value, required, onChange, …); they flow through context onto the rendered <input>. When no id is given, Root generates one.

Checkbox.Input

Renders <input type="checkbox"> and forwards its ref. Reads checked, disabled, size, and indeterminate from Root, so those are not set here.

PropTypeDefaultDescription
onChangeChangeEventHandler<'input'>Called alongside Root's handlers when the box toggles.
namestringOverrides the name inherited from Root.
valuestring'on'Form value submitted when checked.
classNamestringAppended to the input class.

Accepts the rest of ComponentProps<'input'> except size, checked, and id, which come from Root.

Checkbox.Label

Renders a <label> and forwards its ref. Its htmlFor is set to the id from Root, so clicking it toggles the input.

PropTypeDefaultDescription
childrenReactNodeRequired. Label text.
classNamestringAppended to the label class.

Also accepts ComponentProps<'label'>.

Accessibility

  • Root generates a stable id (via useId) and shares it, so Label's htmlFor matches Input's id. The label is announced by screen readers and toggles the box on click.
  • indeterminate sets aria-checked="mixed" on the input.
  • disabled sets the native disabled attribute on the input.

Known limitations

indeterminate only sets aria-checked="mixed" and the mixed visual — it does not set the native HTMLInputElement.indeterminate DOM property, so :indeterminate CSS and native form semantics do not apply.