---
title: Checkbox
description: Select individual options, filters, and consent choices.
group: Components
order: 25
---

Handle independent choices in forms and settings. Keep the control simple, add supporting text, or place it in a selectable card.

```tsx
import * as React from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { cn } from "@/lib/utils"

export function Example() {
  const [checked, setChecked] = React.useState(false)

  return (
    <>
      <label className="flex items-center gap-2.5 text-sm tracking-wide">
        <Checkbox defaultChecked id="basic" />
        Sync local registry on save
      </label>
      <label className="flex items-start gap-2.5">
        <Checkbox id="described" className="mt-0.5" />
        <span className="grid gap-1">
          <span className="text-sm font-medium tracking-wide">
            Watch theme tokens
          </span>
          <span className="text-sm text-muted-foreground">
            Rebuild docs when CSS variables change under globals.css.
          </span>
        </span>
      </label>
      <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">
        <Checkbox disabled id="disabled" />
        Publish package — unlock after review
      </label>
      <label className="block w-full cursor-pointer">
        <Card className={cn("py-0", !checked && "bg-transparent")}>
          <CardContent className="flex items-start gap-2.5 py-3">
            <Checkbox
              id="card"
              checked={checked}
              onCheckedChange={setChecked}
              className="mt-0.5"
            />
            <span className="grid gap-1">
              <span className="text-sm font-medium tracking-wide">
                Include in docs build
              </span>
              <span className="text-sm text-muted-foreground">
                Ships markdown, demos, and tokens with the registry export.
              </span>
            </span>
          </CardContent>
        </Card>
      </label>
    </>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add checkbox
```

## Usage

```tsx
import { Checkbox } from "@/components/ui/checkbox"
```

```tsx
<label className="flex items-center gap-2.5 text-sm tracking-wide">
  <Checkbox id="sync" />
  Sync local registry on save
</label>
```

```tsx
<Checkbox
  checked={checked}
  onCheckedChange={setChecked}
/>
```

## Basic

The common patterns: a plain row, supporting description, disabled state, and selectable card.

```tsx
import * as React from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { cn } from "@/lib/utils"

export function Example() {
  const [checked, setChecked] = React.useState(false)

  return (
    <>
      <label className="flex items-center gap-2.5 text-sm tracking-wide">
        <Checkbox defaultChecked id="basic" />
        Sync local registry on save
      </label>
      <label className="flex items-start gap-2.5">
        <Checkbox id="described" className="mt-0.5" />
        <span className="grid gap-1">
          <span className="text-sm font-medium tracking-wide">
            Watch theme tokens
          </span>
          <span className="text-sm text-muted-foreground">
            Rebuild docs when CSS variables change under globals.css.
          </span>
        </span>
      </label>
      <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">
        <Checkbox disabled id="disabled" />
        Publish package — unlock after review
      </label>
      <label className="block w-full cursor-pointer">
        <Card className={cn("py-0", !checked && "bg-transparent")}>
          <CardContent className="flex items-start gap-2.5 py-3">
            <Checkbox
              id="card"
              checked={checked}
              onCheckedChange={setChecked}
              className="mt-0.5"
            />
            <span className="grid gap-1">
              <span className="text-sm font-medium tracking-wide">
                Include in docs build
              </span>
              <span className="text-sm text-muted-foreground">
                Ships markdown, demos, and tokens with the registry export.
              </span>
            </span>
          </CardContent>
        </Card>
      </label>
    </>
  )
}
```

## Card

Place the control in a `Card` with a title and description. The outlined card fills when selected.

```tsx
import * as React from "react"
import { Card, CardContent } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { cn } from "@/lib/utils"

export function Example() {
  const [checked, setChecked] = React.useState(false)

  return (
    <label className="block w-full cursor-pointer">
      <Card className={cn("py-0", !checked && "bg-transparent")}>
        <CardContent className="flex items-start gap-2.5 py-3">
          <Checkbox
            id="card-option"
            checked={checked}
            onCheckedChange={setChecked}
            className="mt-0.5"
          />
          <span className="grid gap-1">
            <span className="text-sm font-medium tracking-wide">
              Include in docs build
            </span>
            <span className="text-sm text-muted-foreground">
              Ships markdown, demos, and tokens with the registry export.
            </span>
          </span>
        </CardContent>
      </Card>
    </label>
  )
}
```

## Controlled

Pass `checked` and `onCheckedChange` when the parent owns the value. Use `defaultChecked` to set an initial uncontrolled value.

```tsx
import * as React from "react"
import { Checkbox } from "@/components/ui/checkbox"

export function Example() {
  const [checked, setChecked] = React.useState(false)

  return (
    <label className="flex items-center gap-2.5 text-sm tracking-wide">
      <Checkbox
        id="dark-preview"
        checked={checked}
        onCheckedChange={setChecked}
      />
      Preview docs in dark mode {checked ? "(on)" : "(off)"}
    </label>
  )
}
```

## Description

Add a short title and muted description when the choice needs more context.

```tsx
import { Checkbox } from "@/components/ui/checkbox"

export function Example() {
  return (
    <label className="flex items-start gap-2.5">
      <Checkbox id="watch-tokens" className="mt-0.5" />
      <span className="grid gap-1">
        <span className="text-sm font-medium tracking-wide">
          Watch theme tokens
        </span>
        <span className="text-sm text-muted-foreground">
          Rebuild docs when CSS variables change under globals.css.
        </span>
      </span>
    </label>
  )
}
```

## Disabled

Set `disabled` to lock the control. Dim the label as well so the entire row reads as inactive.

```tsx
import { Checkbox } from "@/components/ui/checkbox"

export function Example() {
  return (
    <label className="flex items-center gap-2.5 text-sm tracking-wide opacity-60">
      <Checkbox disabled id="publish-locked" />
      Publish package — unlock after review
    </label>
  )
}
```

## Invalid

Set `aria-invalid` when validation fails, and use a destructive color for the related label or message.

```tsx
import { Checkbox } from "@/components/ui/checkbox"

export function Example() {
  return (
    <label className="flex items-center gap-2.5 text-sm font-medium tracking-wide text-destructive">
      <Checkbox id="license" aria-invalid />
      Accept the license
    </label>
  )
}
```

## Group

Arrange labeled checkboxes in a list for settings with several independent choices.

```tsx
import * as React from "react"
import { Checkbox } from "@/components/ui/checkbox"

const items = [
  { id: "docs", label: "Markdown pages" },
  { id: "demos", label: "Live demos" },
  { id: "tokens", label: "Theme tokens" },
  { id: "changelog", label: "Changelog entries" },
] as const

export function Example() {
  const [selected, setSelected] = React.useState<string[]>(["docs", "demos"])

  function toggle(id: string, next: boolean) {
    setSelected((prev) =>
      next ? [...prev, id] : prev.filter((entry) => entry !== id),
    )
  }

  return (
    <div className="grid gap-3">
      <div className="grid gap-1">
        <p className="text-sm font-medium tracking-wide">
          Include in the docs build
        </p>
        <p className="text-sm text-muted-foreground">
          Pick what ships when you run the registry export.
        </p>
      </div>
      <div className="grid gap-2.5">
        {items.map((item) => (
          <label
            key={item.id}
            className="flex items-center gap-2.5 text-sm tracking-wide"
          >
            <Checkbox
              id={item.id}
              checked={selected.includes(item.id)}
              onCheckedChange={(next) => toggle(item.id, next)}
            />
            {item.label}
          </label>
        ))}
      </div>
    </div>
  )
}
```

## Indeterminate

Set `checked="indeterminate"` on a parent when only some child options are selected. Selecting the parent checks them all.

```tsx
import * as React from "react"
import { Checkbox } from "@/components/ui/checkbox"

const parts = [
  { id: "button", label: "Button" },
  { id: "badge", label: "Badge" },
  { id: "alert", label: "Alert" },
] as const

export function Example() {
  const [selected, setSelected] = React.useState<string[]>(["button"])

  const allChecked = selected.length === parts.length
  const someChecked = selected.length > 0 && !allChecked

  function toggleAll(next: boolean) {
    setSelected(next ? parts.map((part) => part.id) : [])
  }

  function toggle(id: string, next: boolean) {
    setSelected((prev) =>
      next ? [...prev, id] : prev.filter((entry) => entry !== id),
    )
  }

  return (
    <div className="grid gap-3">
      <label className="flex items-center gap-2.5 text-sm font-medium tracking-wide">
        <Checkbox
          id="all-parts"
          checked={allChecked ? true : someChecked ? "indeterminate" : false}
          onCheckedChange={toggleAll}
        />
        Export UI parts
      </label>
      <div className="ms-6 grid gap-2.5">
        {parts.map((part) => (
          <label
            key={part.id}
            className="flex items-center gap-2.5 text-sm tracking-wide"
          >
            <Checkbox
              id={part.id}
              checked={selected.includes(part.id)}
              onCheckedChange={(next) => toggle(part.id, next)}
            />
            {part.label}
          </label>
        ))}
      </div>
    </div>
  )
}
```

## API Reference

### Checkbox

| Prop | Type | Default |
| --- | --- | --- |
| checked | boolean \| "indeterminate" | — |
| defaultChecked | boolean | false |
| onCheckedChange | (checked: boolean) => void | — |
| disabled | boolean | false |
| className | string | — |

```tsx
<Checkbox checked={checked} onCheckedChange={setChecked} />
```

## Source

```tsx
"use client";

import {
  useId,
  useState,
  type ButtonHTMLAttributes,
  type MouseEvent,
} from "react";
import { cn } from "@/lib/utils";

type CheckedState = boolean | "indeterminate";

type CheckboxProps = Omit<
  ButtonHTMLAttributes<HTMLButtonElement>,
  "onChange" | "value" | "children"
> & {
  checked?: CheckedState;
  defaultChecked?: boolean;
  onCheckedChange?: (checked: boolean) => void;
};

function CheckMark({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="3"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M5 13l4 4L19 7" />
    </svg>
  );
}

function IndeterminateMark({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="3"
      strokeLinecap="round"
      aria-hidden="true"
      className={className}
    >
      <path d="M6 12h12" />
    </svg>
  );
}

export function Checkbox({
  checked,
  defaultChecked = false,
  onCheckedChange,
  disabled,
  className,
  id,
  onClick,
  ...props
}: CheckboxProps) {
  const generatedId = useId();
  const inputId = id ?? generatedId;
  const controlled = checked !== undefined;
  const [uncontrolled, setUncontrolled] = useState(defaultChecked);
  const value = controlled ? checked : uncontrolled;
  const indeterminate = value === "indeterminate";
  const isChecked = value === true;

  function toggle() {
    if (disabled) return;
    const next = indeterminate ? true : !isChecked;
    if (!controlled) setUncontrolled(next);
    onCheckedChange?.(next);
  }

  function handleClick(event: MouseEvent<HTMLButtonElement>) {
    onClick?.(event);
    if (!event.defaultPrevented) toggle();
  }

  return (
    <button
      type="button"
      {...props}
      role="checkbox"
      id={inputId}
      data-slot="checkbox"
      data-state={
        indeterminate ? "indeterminate" : isChecked ? "checked" : "unchecked"
      }
      aria-checked={indeterminate ? "mixed" : isChecked}
      disabled={disabled}
      className={cn(
        "peer inline-flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input bg-transparent text-primary-foreground transition-colors duration-200 ease-out disabled:pointer-events-none disabled:opacity-40",
        "data-[state=checked]:border-primary data-[state=checked]:bg-primary",
        "data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary",
        "aria-invalid:border-destructive aria-invalid:bg-destructive/5 aria-invalid:text-destructive",
        className,
      )}
      onClick={handleClick}
    >
      {indeterminate ? (
        <IndeterminateMark className="size-3" />
      ) : isChecked ? (
        <CheckMark className="size-3" />
      ) : null}
    </button>
  );
}
```
