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
childrenReactNode—Required. Usually a Checkbox.Input and a Checkbox.Label.
size'small' | 'medium' | 'large''medium'Scales the box, font size, and spacing.
checkedboolean—Controlled checked state. Pair with onCheckedChange.
defaultCheckedbooleanfalseInitial checked state when uncontrolled.
onCheckedChange(checked: boolean) => void—Called with the next checked value on toggle.
indeterminatebooleanfalseRenders the mixed mark and sets aria-checked="mixed".
disabledboolean—Disables the input and dims the label.
classNamestring—Appended to the container class.
styleCSSProperties—Applied 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.
namestring—Overrides the name inherited from Root.
valuestring'on'Form value submitted when checked.
classNamestring—Appended 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
childrenReactNode—Required. Label text.
classNamestring—Appended 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.