# Checkbox

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/checkbox
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=199-2

```tsx
import { Checkbox } from '@commenda/pathfinder/checkbox';

<Checkbox label="Send me a copy" />;
```


## Installation [#installation]

```sh
pnpm --filter your-app add "@commenda/pathfinder@workspace:*"
```

Import `@commenda/pathfinder/styles.css` once at the app root. The stylesheet is precompiled with Tailwind v4; your application does not need Tailwind or source scanning. Event handlers belong in a client component in Next.js App Router.

## Usage [#usage]

```tsx
import { Checkbox } from '@commenda/pathfinder/checkbox';

<Checkbox
  label="Send me a copy"
  description="We email the filing receipt to you as well."
  checked={sendReceipt}
  onCheckedChange={setSendReceipt}
  name="receipt"
  value="email"
/>;
```

| Prop                   | Type                         | Default / behavior                                                              |
| ---------------------- | ---------------------------- | ------------------------------------------------------------------------------- |
| `label`                | `string`                     | Required, visible text and accessible name                                      |
| `hideLabel`            | `boolean`                    | `false`; retains the accessible name for dense/table controls                   |
| `description`          | `string`                     | Optional supporting text, associated with the control                           |
| `checked`              | `boolean`                    | Controlled selection; pair with `onCheckedChange` or `readOnly`                 |
| `defaultChecked`       | `boolean`                    | `false`; initial uncontrolled selection                                         |
| `onCheckedChange`      | `(checked: boolean) => void` | Receives the next boolean only                                                  |
| `indeterminate`        | `boolean`                    | `false`; mixed parent selection, separate from `checked`                        |
| `disabled`             | `boolean`                    | Blocks interaction and submission                                               |
| `disabledReason`       | `string`                     | Visible explanation while disabled, included in accessible description          |
| `readOnly`             | `boolean`                    | Prevents changes while retaining value                                          |
| `required`             | `boolean`                    | Requires selection for form validation                                          |
| `variant`              | `'default' \| 'card'`        | `'default'`; card retains checkbox semantics                                    |
| `details`              | `readonly string[]`          | Optional explanatory bullet lines, intended for cards                           |
| `name`                 | `string`                     | Name used when the control submits its value with a form.                       |
| `value`                | `string`                     | Value submitted when checked; defaults to 'on'.                                 |
| `uncheckedValue`       | `string`                     | Value submitted when unchecked; omitted when not configured.                    |
| `form`                 | `string`                     | ID of the form associated with the control.                                     |
| `id`                   | `string`                     | ID of the underlying form input.                                                |
| `inputRef`             | `Ref<HTMLInputElement>`      | Access to the underlying form input.                                            |
| `className`            | `string`                     | Additional class names for the outer layout.                                    |
| `ref`                  | `Ref<HTMLElement>`           | Access to the interactive root element.                                         |
| Native events and ARIA | Base UI root props           | Typed passthrough; arbitrary children/render overrides are intentionally hidden |

A checkbox saves with a form. Its label is part of the target. `indeterminate` is a parent summary, not a third answer: the application derives it from children and updates the children on change. Do not pass `checked` and `defaultChecked` together. Use positive labels such as “Send me a copy”. A visible description is preferable to a tooltip-only disabled reason for form controls.

The Figma control is one size: a 16px box, 4px radius, 12px check/minus mark and 8px label gap. There is no invented size or cosmetic hover/focus state prop. Checkbox cards reuse the same component with `variant="card"`.

## Examples [#examples]

### Checked, unchecked and mixed [#checked-unchecked-and-mixed]


```tsx
<>
  <Checkbox label="Unchecked" />
  <Checkbox label="Checked" defaultChecked />
  <Checkbox
    label="Some filings selected"
    indeterminate={mixed}
    onCheckedChange={() => setMixed(false)}
  />
</>;
```


### Description [#description]


```tsx
<Checkbox
  label="Send me a copy"
  description="We email the filing receipt to you as well."
/>;
```


### Parent and children [#parent-and-children]


```tsx
const filings = ['Annual filing', 'Quarterly filing', 'Monthly filing'];
const [selected, setSelected] = useState(['Annual filing']);

<>
  <Checkbox
    label="All filings"
    checked={selected.length === filings.length}
    indeterminate={selected.length > 0 && selected.length < filings.length}
    onCheckedChange={(checked) => setSelected(checked ? [...filings] : [])}
  />
  {filings.map((label) => (
    <Checkbox
      key={label}
      label={label}
      checked={selected.includes(label)}
      onCheckedChange={(checked) =>
        setSelected((current) =>
          checked
            ? [...current, label]
            : current.filter((item) => item !== label)
        )
      }
    />
  ))}
</>;
```


### Checkbox card [#checkbox-card]


```tsx
<Checkbox
  variant="card"
  label="Send me a copy"
  description="We email the filing receipt to you as well."
  details={['Due 15 April', 'Includes state apportionment']}
/>;
```


### Disabled states [#disabled-states]


```tsx
<>
  <Checkbox label="Unchecked" disabled />
  <Checkbox label="Checked" defaultChecked disabled />
  <Checkbox label="Some filings selected" indeterminate disabled />
</>;
```


Read the [Figma choice-control rules](/docs/components/checkbox#design-rules) for design guidance, source discrepancies and accessibility requirements.

## Design rules [#design-rules]

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

### Checkbox [#checkbox]

Source: [199:2](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=199-2), variants 199:90, guidance 203:226.

- **CHECKBOX-01** — Use independent checkboxes for choices saved with a form. An immediate setting uses Switch. The label is part of the target. Use positive labels such as “Send me a copy”, not questions.
- **CHECKBOX-02** — Mixed/indeterminate summarizes a parent whose children are partly selected. Activating it selects all its children. It is never a third answer. Applications own the child selection and derive the parent's checked/mixed flags.
- **CHECKBOX-03** — Box 16px, radius 4px, 1px soft-pearl-400 border. Hover uses soft-pearl-500. Checked/mixed uses blue-700 and a white 12px check/minus; hover blue-800. Keyboard focus adds a white 2px gap and blue 2px ring. Disabled uses soft-pearl-100 surface, soft-pearl-300 border and soft-pearl-600 mark/text.
- **CHECKBOX-04** — Label/13 regular sits 8px from the box; Copy/12 description uses soft-pearl-800, 2px below. Child rows indent 24px. Long content wraps and must not clip the box or focus ring.

### Radio and cards [#radio-and-cards]

Source: [199:3](https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=199-3), radio variants 200:46, card variants 201:413, guidance 205:320.

- **RADIO-01** — One choice from two to five visible options. Use Select for longer lists or limited space. A lone boolean option uses Checkbox/Switch. Arrow keys move selection and skip disabled items; Tab enters/leaves the group.
- **RADIO-02** — Prefer a sensible default. Leave the group unanswered only when deliberate choice is required. Never silently choose the first value or replace a removed controlled value. The application reconciles dynamic options.
- **RADIO-03** — Order options by frequency, size or time rather than alphabetically by default. Circle 16px, selected white dot 6px; colors and focus match Checkbox. Group row gap 8px.
- **CARD-01** — Use a card when each option needs title and description. Keep cards in one row equal-height. The “Radio card” Figma collection contains both radio and checkbox controls; Pathfinder exposes `RadioGroup variant="card"` for one choice and `Checkbox variant="card"` for independent choices.
- **CARD-02** — Card padding 16px, control/text gap 12px, radius 8px. Title Label/13 Strong; description Copy/12 with 4px gap. Detail bullets have 2px gap and 4px top padding. Surface remains white when selected; border becomes blue-700. Focus surrounds the whole card. Disabled surface/border/text use soft-pearl-100/300/600. The 320px specimen width adapts to the container.

### Implementation decisions and source differences [#implementation-decisions-and-source-differences]

The complete prop tables are on [Checkbox](/docs/components/checkbox), [RadioGroup](/docs/components/radio-group) and [Switch](/docs/components/switch). Source rule IDs are also kept in the component implementation comments and exported through `llms.txt`, `llms-full.txt` and the Markdown routes.

- **CHOICE-01** — The sheets describe 8px list gaps but some documentation frames place rows 32px apart. Use the documented 8px component-list gap; documentation specimen whitespace is not a new size token.

- **CHOICE-02** — Built-in text is plain, wrapping content. Do not place links, buttons or nested interactive controls inside labels or cards. Disabled reasons are visible descriptions, not tooltip-only explanations on controls removed from keyboard navigation.

- **CHOICE-03** — Visual control size is not the hit target. Coarse-pointer rows provide at least 44px height, while preserving 16px marks and the 36×20px switch track. Cards already exceed this height.

- **CHOICE-04** — Hover/focus are real browser states, not public cosmetic props. Retain native `name`, `value`, `form`, `required`, `readOnly`, disabled semantics and input refs. `hideLabel` preserves the required accessible label.

- **CHOICE-05** — Use Pathfinder's existing Check/Minus optical artwork at 12px. Boxes, circles, tracks and thumbs are interactive primitive geometry, styled with the Figma tokens; they are not flattened screenshots. This keeps focus, RTL and switch movement functional.
