Askr
UI & Components

Checkbox

Use checkbox with the current published Askr packages.

  • @askrjs/ui/checkbox0.0.13
  • @askrjs/themes/checkbox0.0.13

How to use checkbox

Checkbox should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep checkbox labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Checkbox, Field, FieldLabel } from '@askrjs/themes/components';

<Field>
  <Checkbox id="email-updates" checked={enabled()} onCheckedChange={enabled.set} />
  <FieldLabel for="email-updates">Email project updates</FieldLabel>
</Field>

Purpose

A binary or tri-state selection — checked, unchecked, or visually indeterminate — is what Checkbox exists to represent as a headless primitive. Notably, onClick is excluded from its prop types entirely; onPress is the sole interaction callback, routed through the same pressable foundation Button relies on. That shared foundation keeps activation behavior consistent across the library instead of each component reimplementing its own.

Install and import

Checkbox's headless behavior comes from @askrjs/ui and @askrjs/askr, and @askrjs/themes layers the default styling on top if you want it. `import { Checkbox } from '@askrjs/ui/checkbox'` gets you the headless primitive, while the same import from `@askrjs/themes/checkbox` swaps in the pre-styled version. That themes import is a direct re-export of the @askrjs/ui Checkbox, not a separate wrapper, so nothing about its behavior changes between the two.

Live examples

Step through an unchecked, checked, and indeterminate example side by side — indeterminate is worth its own example since it's easy to confuse with checked. A disabled checkbox and an asChild example (a custom div rendered as role="checkbox") round out the set, along with a "select all" pattern that toggles indeterminate based on partial selection.

Anatomy

By default Checkbox renders a single native <input type="checkbox">. With asChild, it instead applies its checked/pressed behavior to the single child you provide — but unlike Button or Input's asChild mode, you're responsible for adding role="checkbox" to that child yourself, since Checkbox explicitly will not add role="button" or infer the role for you.

State model

Checked state can be controlled (checked + onCheckedChange) or uncontrolled (defaultChecked), matching standard controlled/uncontrolled React-style conventions. indeterminate is a separate, purely visual prop that layers on top of checked rather than replacing it — a checkbox can be checked=false and indeterminate=true at the same time, and toggling indeterminate off doesn't change the underlying checked value.

Keyboard and accessibility

Enter and Space activate the checkbox in a way consistent with how WAI-ARIA specifies checkbox behavior, sourced from the browser on native inputs and from the pressable foundation on asChild targets. The aria-checked attribute reflects 'true', 'false', or 'mixed' for indeterminate — except on the native-input path, which currently skips aria-checked for indeterminate and relies on data-state="indeterminate" instead. Keep that in mind if you're styling or testing indeterminate state on a native checkbox: check the data attribute, not aria-checked.

Styling and tokens

The themed checkbox targets [data-slot="checkbox"], rendering as a 1rem square box with appearance: none and its own check-mark and dash SVGs drawn as background-image for the checked and indeterminate states respectively — there's no separate icon component to swap in. data-state="checked" or aria-checked="true" both trigger the checked look, and data-disabled dims the box and disables pointer events.

API

CheckboxOwnProps includes checked?: boolean, defaultChecked?: boolean, onCheckedChange?: (checked: boolean) => void, indeterminate?: boolean, disabled?: boolean, required?: boolean, name?: string, value?: string, and onPress. CheckboxInputProps layers these onto native <input> attributes (minus the ones Checkbox owns, like checked and onClick) with asChild?: false, while CheckboxAsChildProps requires asChild: true and a single child.

Edge cases

Setting indeterminate=true does not clear or override the checked value underneath it — if you toggle indeterminate off without also setting checked, the checkbox will show whatever checked was already holding, which surprises people expecting indeterminate to reset the value. Also remember the asChild path needs role="checkbox" added manually; skipping it leaves the element with no accessible role at all, unlike Button's asChild which preserves the child's own semantics.

See Radio Group for mutually-exclusive selection instead of independent toggles, Switch for a checkbox-like control styled as an on/off toggle, and Form, Label, Field, and Input Group for pairing Checkbox with a label and description.