Checkbox
Primitive

Checkbox

Checkbox state with indeterminate support and optional native form participation.

Live behavior

Checkbox in motion

Interactive
Atom behavior · App-owned appearance

Toggle the control with Space and inspect its checked state.

waiting for input

When to Use

Use Checkbox for an independent choice that can be checked or unchecked, such as accepting terms or selecting an item. Use CheckboxGroup for several related choices, Switch for an on/off setting that takes effect immediately, and Toggle for a pressed command state such as bold text.

Features

The behavior Atom owns before your product adds appearance.

  • Supports checked, unchecked, and indeterminate states.
  • Supports controlled and uncontrolled state.
  • Supports disabled, read-only, invalid, and required state.
  • Renders a hidden native checkbox input for form submission when name is provided.
  • Exposes checkbox state through ARIA and data attributes.
  • Supports a decorative Indicator with optional force mounting.
  • Supports asChild and render on both parts.

Import

tsx
import { Checkbox } from "@flowstack-ui/atom";

Anatomy

tsx
<Checkbox.Root>
  <Checkbox.Indicator />
</Checkbox.Root>

API Reference

Root

Renders a button with checkbox semantics and owns the checked state. Root also renders an assistive-technology-hidden native checkbox when name or required needs native form behavior.

PropTypeDefault
checkedboolean | "indeterminate"-
defaultCheckedboolean | "indeterminate"false
onCheckedChange(checked: CheckboxCheckedState) => void-
disabledbooleanfalse
readOnlybooleanfalse
invalidbooleanfalse
requiredbooleanfalse
validationBehavior"inline" | "native"Field/Form value or "native"
namestring-
valuestring"on"
formstring-
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
role"checkbox"
aria-checked"true" | "false" | "mixed"
aria-labelNative value when provided
aria-disabled"true" when disabled
aria-required"true" when required
aria-invalid"true" when invalid
aria-readonly"true" when read-only
Data attributeValues
[data-slot]"checkbox"
[data-state]"checked" | "unchecked" | "indeterminate"
[data-disabled]Present when disabled
[data-readonly]Present when read-only
[data-invalid]Present when invalid

The transparent native proxy receives name, value, form, checked, disabled, and required from Root. It is aligned to the visible Root and remains eligible for native constraint validation even without name; read-only behavior belongs to the visible semantic Root and does not place a readonly attribute on the proxy. Native validation focus is redirected to the visible Root. Indeterminate is an ARIA state and does not submit the hidden input as checked. A disabled Root exposes aria-disabled; the default button also uses the native disabled attribute. Read-only Root remains focusable but does not toggle.

Indicator

Renders a decorative span for the current checked or indeterminate state. It renders no DOM while unchecked unless forceMount is true.

PropTypeDefault
forceMountbooleanfalse
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
aria-hidden"true"
Data attributeValues
[data-slot]"checkbox-indicator"
[data-state]"checked" | "unchecked" | "indeterminate"
[data-disabled]Present when Root is disabled

Examples

Indeterminate Selection

tsx
import { Checkbox } from "@flowstack-ui/atom";

export function SelectAllCheckbox() {
  return (
    <Checkbox.Root
      defaultChecked="indeterminate"
      aria-label="Select all messages"
    >
      <Checkbox.Indicator>Selected</Checkbox.Indicator>
    </Checkbox.Root>
  );
}

Form Submission

tsx
import { Checkbox } from "@flowstack-ui/atom";

export function TermsCheckbox() {
  return (
    <form>
      <Checkbox.Root name="terms" value="accepted" required>
        <Checkbox.Indicator>Accepted: </Checkbox.Indicator>
        Accept the terms
      </Checkbox.Root>
      <button type="submit">Create account</button>
    </form>
  );
}

Accessibility

An untouched required Checkbox remains visually neutral. Leaving it unchecked after focus, removing its checked state after interaction, or attempting validation mirrors proxy invalidity to the visible Root and its Field. Correction clears the derived invalid state immediately, and form reset returns it to untouched. Inline behavior suppresses the browser bubble and uses Field Error when available; native behavior keeps the aligned browser UI. When inline validation redirects focus to Root, it explicitly scrolls Root into view and keeps [data-focus-visible] present until blur so a styled layer can expose the focus move.

Checkbox follows the WAI-ARIA Checkbox pattern. Root exposes role="checkbox" and its state through aria-checked; the indeterminate state is announced as mixed. Provide an accessible name through visible text, native aria-label, or aria-labelledby. Inside Field.Root, Checkbox inherits the control ID, state, and description relationships unless an explicit control prop overrides that state. Uncontrolled checked state returns to defaultChecked on native form reset.

Indicator is decorative and hidden from assistive technology because Root already communicates the state. Disabled Root is removed from interaction; read-only Root remains focusable so its value can still be inspected.

KeyDescription
SpaceToggles checked state unless disabled or read-only.
EnterToggles checked state unless disabled or read-only.

Changelog

0.6.16

  • Explicitly scrolled the visible Root into view when inline validation directs focus there.

0.6.15

  • Exposed inline validation-directed focus on the visible Root through [data-focus-visible] until blur.

0.6.14

  • Revealed required invalid state after the visible checkbox loses focus or an interacted checkbox becomes unchecked, while preserving neutral initial and reset states.

0.6.13

  • Mirrored aligned-proxy validity to the visible Checkbox, Field, and Form with inline Error presentation or opt-in native browser UI.

0.6.12

  • Aligned the native validation proxy with Root, redirected validation focus, and supported required validity without a submission name.

0.6.11

  • Restored native required validation by keeping the controlled hidden form input eligible for browser constraint validation.

0.6.0

  • Exposed aria-disabled="true" consistently on disabled Root composition, including non-button asChild and render targets.

0.5.0

  • Added Field state, generated control ID, and description integration; removed ariaLabel in favor of native ARIA; uncontrolled state now follows native form reset while submission and required validity remain native.

0.1.0

  • Initial Atom release with root, indicator, indeterminate state, and optional form input.