# Checkbox (/docs/checkbox)





`Checkbox` is a single boolean control with an inline label. The box reuses the family's shared control surface, so it sits flush beside an [`Input`](/docs/input), and `description` / `error` / `required` are built in via [`Field`](/docs/field). It implements the kit's form-control contract — `name` submits, and `ref` / `onBlur` land on the hidden input — so it binds to react-hook-form, TanStack Form, or a plain `<form>` with no glue.

```tsx
import { Checkbox } from '@42-central/ui-react/checkbox';

<Checkbox label="Follow the Norm" />
```

## Playground [#playground]

<story.Playground />

## Sizes [#sizes]

Five sizes — `xs` through `xl` — matching the rest of the family. The box and its mark scale together.

<Preview align="block" code="&#x22;<Checkbox size=\&#x22;xs\&#x22; label=\&#x22;xs\&#x22; defaultChecked />\n<Checkbox size=\&#x22;sm\&#x22; label=\&#x22;sm\&#x22; defaultChecked />\n<Checkbox size=\&#x22;md\&#x22; label=\&#x22;md\&#x22; defaultChecked />\n<Checkbox size=\&#x22;lg\&#x22; label=\&#x22;lg\&#x22; defaultChecked />\n<Checkbox size=\&#x22;xl\&#x22; label=\&#x22;xl\&#x22; defaultChecked />&#x22;">
  <div className="flex flex-col gap-3">
    <Checkbox size="xs" label="xs" />

    <Checkbox size="sm" label="sm" />

    <Checkbox size="md" label="md" />

    <Checkbox size="lg" label="lg" />

    <Checkbox size="xl" label="xl" />
  </div>
</Preview>

## Variants [#variants]

Two `variant`s decide the checked fill. `default` is a neutral, monochrome box that stays palette-independent; `filled` tints the checked fill with `color`.

<Preview align="block" code="&#x22;<Checkbox variant=\&#x22;default\&#x22; label=\&#x22;Default\&#x22; defaultChecked />\n<Checkbox variant=\&#x22;filled\&#x22; color=\&#x22;brand\&#x22; label=\&#x22;Filled\&#x22; defaultChecked />&#x22;">
  <div className="flex flex-col gap-3">
    <Checkbox variant="default" label="Default" />

    <Checkbox variant="filled" color="brand" label="Filled" />
  </div>
</Preview>

## Colors [#colors]

`color&#x60; tints the checked box in the &#x2A;*`filled`** variant — the `default` variant stays monochrome. The resting box is palette-independent, and the focus ring is always the brand ring. Set `data-color` once on the root and every kit palette tints for free.

<Preview align="block" code="&#x22;<Checkbox variant=\&#x22;filled\&#x22; color=\&#x22;brand\&#x22; label=\&#x22;brand\&#x22; defaultChecked />\n<Checkbox variant=\&#x22;filled\&#x22; color=\&#x22;green\&#x22; label=\&#x22;green\&#x22; defaultChecked />\n<Checkbox variant=\&#x22;filled\&#x22; color=\&#x22;blue\&#x22; label=\&#x22;blue\&#x22; defaultChecked />\n<Checkbox variant=\&#x22;filled\&#x22; color=\&#x22;purple\&#x22; label=\&#x22;purple\&#x22; defaultChecked />\n<Checkbox variant=\&#x22;filled\&#x22; color=\&#x22;red\&#x22; label=\&#x22;red\&#x22; defaultChecked />&#x22;">
  <div className="flex flex-wrap gap-4">
    <Checkbox variant="filled" color="brand" label="brand" />

    <Checkbox variant="filled" color="green" label="green" />

    <Checkbox variant="filled" color="blue" label="blue" />

    <Checkbox variant="filled" color="purple" label="purple" />

    <Checkbox variant="filled" color="red" label="red" />
  </div>
</Preview>

## Disabled & indeterminate [#disabled--indeterminate]

`disabled` dims the control and blocks interaction. The tri-state `checked="indeterminate"` renders a partial (minus) mark — drive it from the parent, e.g. a "select all" header whose children are partly checked. It never sets itself.

<Preview align="block" code="&#x22;<Checkbox label=\&#x22;Disabled\&#x22; disabled />\n<Checkbox label=\&#x22;Disabled, checked\&#x22; disabled defaultChecked />\n<Checkbox label=\&#x22;Select all projects\&#x22; checked=\&#x22;indeterminate\&#x22; />&#x22;">
  <div className="flex flex-col gap-3">
    <Checkbox label="Disabled" />

    <Checkbox label="Disabled, checked" />

    <Checkbox label="Select all projects" checked="indeterminate" />
  </div>
</Preview>

## With description & error [#with-description--error]

`description` (helper text) and `error` sit below the control via the built-in [`Field`](/docs/field). Passing `error` flips the box to the invalid (red) state; `required` adds the semantic flag and a red asterisk on the label.

<Preview align="block" code="&#x22;<Checkbox\n  label=\&#x22;Evaluation reminders\&#x22;\n  description=\&#x22;We'll only ping you about peer evaluations.\&#x22;\n  defaultChecked\n/>\n\n<Checkbox label=\&#x22;I accept the Code of Conduct\&#x22; required />\n\n<Checkbox label=\&#x22;I accept the Code of Conduct\&#x22; error=\&#x22;You must accept to continue\&#x22; />&#x22;">
  <div className="flex flex-col gap-5">
    <Checkbox label="Evaluation reminders" description="We'll only ping you about peer evaluations." />

    <Checkbox label="I accept the Code of Conduct" />

    <Checkbox label="I accept the Code of Conduct" error="You must accept to continue" />
  </div>
</Preview>

## Controlled [#controlled]

Drive the checked state from state. `onCheckedChange` hands you the next value — `boolean`, or `"indeterminate"` if you've set that.

```tsx
'use client';
import { useState } from 'react';
import { Checkbox } from '@42-central/ui-react/checkbox';

export function TermsCheckbox() {
  const [checked, setChecked] = useState(false);
  return (
    <Checkbox
      label="Follow the Norm"
      checked={checked}
      onCheckedChange={(next) => setChecked(next === true)}
    />
  );
}
```

## Forms [#forms]

`Checkbox` renders a hidden native `<input type="checkbox">`, so a `name` wires it into native `<form>` submission and `FormData`. The submitted `value` defaults to `"on"`.

```tsx
<form action={save}>
  <Checkbox name="events" value="yes" label="Subscribe to 42 events" />
</form>
```

## API [#api]

<TypeTable
  type="{
  checked: {
    type: 'boolean | &#x22;indeterminate&#x22;',
    description: 'Controlled checked state.',
  },
  defaultChecked: {
    type: 'boolean | &#x22;indeterminate&#x22;',
    description: 'Uncontrolled initial checked state.',
  },
  onCheckedChange: {
    type: '(checked: boolean | &#x22;indeterminate&#x22;) => void',
    description: 'Fires with the next checked state.',
  },
  label: { type: 'ReactNode', description: 'Inline label beside the box.' },
  description: { type: 'ReactNode', description: 'Field helper text below the control.' },
  error: { type: 'ReactNode', description: 'Field error; sets the invalid state.' },
  required: { type: 'boolean', default: 'false', description: 'Required flag + asterisk.' },
  invalid: { type: 'boolean', default: 'false', description: &#x22;Red invalid state, OR'd with error.&#x22; },
  disabled: { type: 'boolean', default: 'false', description: 'Disable the control.' },
  readOnly: { type: 'boolean', default: 'false', description: 'Keep contrast but reject changes.' },
  size: { type: '&#x22;xs&#x22; | &#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;', default: '&#x22;md&#x22;', description: 'Box + mark sizing.' },
  variant: { type: '&#x22;default&#x22; | &#x22;filled&#x22;', default: '&#x22;default&#x22;', description: 'Checked fill: monochrome (default) or palette-colored (filled).' },
  color: { type: 'Color', default: '&#x22;brand&#x22;', description: 'Checked-fill accent in the filled variant.' },
  name: { type: 'string', description: 'Name for the hidden input (form submission).' },
  value: { type: 'string', default: '&#x22;on&#x22;', description: 'Submitted value when checked.' },
  classNames: { type: '{ root?, control?, label?, field? }', description: 'Per-part class overrides.' },
  testId: { type: 'string', description: 'data-testid on the root — the clickable control Ark renders, not the hidden native checkbox input. See the Testing guide.' },
}"
/>

## Accessibility [#accessibility]

* Full Checkbox model from Ark: `Space` toggles, focus shows the ring, and the box / label / description / error associations are wired (the latter via the built-in `Field`).
* A hidden native `<input type="checkbox">` backs form submission and exposes `role="checkbox"` with `aria-checked` (incl. `"mixed"` for the indeterminate state).
* `invalid` (or `error`) sets `aria-invalid` on the input.
* `testId` renders as `data-testid` on that same root element, never the hidden native input. See the [Testing](/docs/testing) guide.
* RTL is honored via the document `dir` — the box and label mirror automatically.
