# Checkbox

A control for an on/off choice that applies on submit.

```tsx
import { Checkbox, Label } from 'ferry-ui'

const EVENTS = [
  { value: 'invoice-paid', label: 'A customer pays an invoice', checked: true },
  { value: 'payment-failed', label: 'A payment fails', checked: true },
  { value: 'member-joined', label: 'A member joins the workspace', checked: false },
]

export default function CheckboxHero() {
  return (
    <fieldset className="flex flex-col gap-3">
      <legend className="mb-3 text-sm font-medium text-foreground">Send me an email when</legend>
      {EVENTS.map((event) => (
        <Label key={event.value} className="font-normal">
          <Checkbox defaultChecked={event.checked} />
          {event.label}
        </Label>
      ))}
    </fieldset>
  )
}
```

## Usage guidelines

- **A choice that applies on submit.** Use it to accept terms, to select more than one value, or to select table rows.
- **Each checkbox is independent.** The state of one checkbox does not change the others.
- **Give each checkbox a name.** Wrap it in a [Label](/docs/components/label). If it has no visible text, pass `aria-label`.

| Choice | Control |
| --- | --- |
| A choice that applies on submit, or more than one value | `Checkbox` |
| A setting that applies immediately | [Switch](/docs/components/switch) |
| One value among 2 to 5 options | [Radio Group](/docs/components/radio-group) |
| A button that stays pressed in a toolbar | [Toggle](/docs/components/toggle) |

## Anatomy

Import the component. A checkbox has one part.

```tsx title="Anatomy"

<Checkbox />
```

## Examples

### Label and description

A `Label` around the checkbox makes the text a part of the click area.

To add a description, link the label with `htmlFor`. Link the description with `aria-describedby`.

```tsx
import { Checkbox, Label } from 'ferry-ui'

export default function CheckboxDescription() {
  return (
    <div className="flex max-w-sm items-start gap-2.5">
      <Checkbox id="weekly-report" aria-describedby="weekly-report-hint" className="mt-0.5" />
      <div className="flex flex-col gap-0.5">
        <Label htmlFor="weekly-report">Email me a weekly report</Label>
        <p id="weekly-report-hint" className="text-[13px] text-foreground-light">
          Each Monday, the report goes to all the admins of the workspace.
        </p>
      </div>
    </div>
  )
}
```

### States

`defaultChecked` sets the first state. The value `'indeterminate'` shows a dash. `disabled` stops all clicks.

```tsx
import { Checkbox, Label } from 'ferry-ui'

export default function CheckboxStates() {
  return (
    <div className="flex flex-col gap-3">
      <Label>
        <Checkbox />
        Unchecked
      </Label>
      <Label>
        <Checkbox defaultChecked />
        Checked
      </Label>
      <Label>
        <Checkbox defaultChecked="indeterminate" />
        Indeterminate
      </Label>
      <Label>
        <Checkbox disabled />
        Disabled
      </Label>
      <Label>
        <Checkbox disabled defaultChecked />
        Disabled and checked
      </Label>
    </div>
  )
}
```

### Controlled state

A checkbox holds its state by default. To control the state, pass `checked` and `onCheckedChange`.

`onCheckedChange` gets `true`, `false` or `'indeterminate'`.

```tsx
import * as React from 'react'
import { Checkbox, Label, Textarea } from 'ferry-ui'

export default function CheckboxControlled() {
  const [withNote, setWithNote] = React.useState(false)

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <Label>
        <Checkbox checked={withNote} onCheckedChange={(checked) => setWithNote(checked === true)} />
        Add a note to the invoice
      </Label>
      {withNote && <Textarea aria-label="Note" placeholder="Thank you for your order." />}
    </div>
  )
}
```

### Select all

Use `'indeterminate'` when the user selects only some items of a list. A click on an indeterminate checkbox sends `true`.

```tsx
import * as React from 'react'
import { Checkbox, Label } from 'ferry-ui'

const MEMBERS = [
  { id: 'maya', name: 'Maya Chen' },
  { id: 'sam', name: 'Sam Lee' },
  { id: 'nora', name: 'Nora Diaz' },
]

export default function CheckboxSelectAll() {
  const [selected, setSelected] = React.useState(['maya'])
  const all = selected.length === MEMBERS.length
  const some = selected.length > 0 && !all

  function toggle(id: string, checked: boolean) {
    setSelected((current) => (checked ? [...current, id] : current.filter((item) => item !== id)))
  }

  return (
    <div className="flex w-full max-w-xs flex-col divide-y rounded-lg border bg-surface-100">
      <Label className="px-4 py-2.5">
        <Checkbox
          checked={all ? true : some ? 'indeterminate' : false}
          onCheckedChange={(checked) => setSelected(checked === true ? MEMBERS.map((member) => member.id) : [])}
        />
        All members
      </Label>
      {MEMBERS.map((member) => (
        <Label key={member.id} className="px-4 py-2.5 font-normal">
          <Checkbox
            checked={selected.includes(member.id)}
            onCheckedChange={(checked) => toggle(member.id, checked === true)}
          />
          {member.name}
        </Label>
      ))}
    </div>
  )
}
```

### Form submission

In a `<form>`, `name` and `value` add the checkbox to the data of the form. `required` makes the checkbox mandatory.

```tsx
import * as React from 'react'
import { Button, Checkbox, Label } from 'ferry-ui'

const PERMISSIONS = [
  { value: 'read', label: 'Read projects' },
  { value: 'write', label: 'Create and edit projects' },
  { value: 'billing', label: 'Manage billing' },
]

export default function CheckboxForm() {
  const [sent, setSent] = React.useState<string>()

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    // The form holds one "permissions" entry for each checked checkbox.
    const values = new FormData(event.currentTarget).getAll('permissions')
    setSent(values.length > 0 ? values.join(', ') : 'no permission')
  }

  return (
    <form onSubmit={submit} className="flex flex-col items-start gap-4">
      <fieldset className="flex flex-col gap-3">
        <legend className="mb-3 text-sm font-medium text-foreground">Permissions of the API key</legend>
        {PERMISSIONS.map((permission) => (
          <Label key={permission.value} className="font-normal">
            <Checkbox name="permissions" value={permission.value} defaultChecked={permission.value === 'read'} />
            {permission.label}
          </Label>
        ))}
      </fieldset>
      <Button type="submit">Create key</Button>
      <p role="status" className="text-[13px] text-foreground-light">
        {sent !== undefined && `The form sent: ${sent}`}
      </p>
    </form>
  )
}
```

### Error

`aria-invalid` shows the error border. Show the error message as text near the checkbox.

```tsx
import * as React from 'react'
import { Button, Checkbox, Label, toast } from 'ferry-ui'

export default function CheckboxInvalid() {
  const [accepted, setAccepted] = React.useState(false)
  const [error, setError] = React.useState(false)

  function submit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setError(!accepted)
    if (accepted) toast.success('Workspace created')
  }

  return (
    <form noValidate onSubmit={submit} className="flex flex-col items-start gap-3">
      <Label>
        <Checkbox
          checked={accepted}
          onCheckedChange={(checked) => {
            setAccepted(checked === true)
            setError(false)
          }}
          aria-invalid={error ? true : undefined}
          aria-describedby={error ? 'terms-error' : undefined}
        />
        I accept the terms of service
      </Label>
      {error && (
        <p id="terms-error" role="alert" className="text-[13px] text-destructive">
          Accept the terms to continue.
        </p>
      )}
      <Button type="submit" variant="primary">
        Create workspace
      </Button>
    </form>
  )
}
```

## Accessibility

- <Kbd>Space</Kbd> changes the state of the checkbox that has the focus.
- A checkbox with no visible text must have an `aria-label`, for example in a table cell.
- Put a group of checkboxes in a `<fieldset>` with a `<legend>`.

## API reference

`Checkbox` also accepts the props of the Radix UI Checkbox primitive and each attribute of the `<button>` element.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `"indeterminate" \| boolean` |  |  |
| `defaultChecked` | `"indeterminate" \| boolean` |  |  |
| `required` | `boolean` |  |  |
| `onCheckedChange` | `((checked: CheckedState) => void)` |  |  |
| `asChild` | `boolean` |  |  |
