Headless / Selection

Checkbox

Checkbox is a headless boolean input. It renders a real <input type="checkbox">, so native form submission, autofill, and reset work without extra wiring — and it adds an indeterminate state.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A forwardRef wrapper over <input type="checkbox">. It is not a compound component — there is no Root or Trigger, just a single element. Because the underlying input is real, the checkbox posts with a <form>, restores on reset, and is reached by the browser's own affordances. You style around it; you do not replace it.

The one thing Checkbox adds over a bare input is the indeterminate state — the visual "some but not all" used for a parent checkbox over a group. Browsers expose indeterminate only as a DOM property, never an attribute; Checkbox sets it through the ref and mirrors it as aria-checked="mixed".

Install

example.tsx
yarn add @usemotif/headless
example.tsx
import { Checkbox } from '@usemotif/headless';

API

Checkbox

stable
const Checkbox: ForwardRefExoticComponent<CheckboxProps & RefAttributes<HTMLInputElement>>
indeterminateboolean

The mixed state. Set on the DOM input through the ref; rendered as `aria-checked="mixed"`.

invalidboolean

Marks the checkbox invalid — sets `aria-invalid`.

…InputHTMLAttributesOmit<InputHTMLAttributes, "type">

Every standard input attribute except `type`. `checked`, `defaultChecked`, `onChange`, `name`, `disabled`, `required`, and the rest pass through.

Accessibility

Checkbox is a real <input type="checkbox">, so the role, the checked state, and keyboard operation — Space to toggle — come from the platform. Give it an accessible name: wrap it in a <label>, or pair it with a Label and matching htmlFor.

indeterminate is a presentation state, not a third value. It surfaces as aria-checked="mixed" for assistive technology, but the input still submits as checked or unchecked. Use it for a parent checkbox summarising a group; clicking it should resolve the group to all-on or all-off.

Examples

A labelled checkbox:

example.tsx
<label>
<Checkbox name="terms" required />
I accept the terms
</label>

A parent checkbox over a group:

example.tsx
<Checkbox
checked={allChecked}
indeterminate={someChecked && !allChecked}
onChange={toggleAll}
/>