Checkbox

A styled checkbox component built on Radix UI Checkbox. Supports checked, unchecked, and indeterminate states with visual icons, and integrates with form labels for full accessibility.

Installation

npm install @radix-ui/react-checkbox

Usage

import { Checkbox } from "@/components/ui/checkbox";

Examples

Basic with Label

Pair the Checkbox with a <Label> using matching id and htmlFor attributes. This ensures screen readers announce the label when the checkbox is focused.

import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
 
export function BasicCheckbox() {
  return (
    <div className="flex items-center gap-2">
      <Checkbox id="terms" />
      <Label htmlFor="terms">Accept terms and conditions</Label>
    </div>
  );
}

Indeterminate State

Pass "indeterminate" to the checked prop to show a minus icon. This is useful for "select all" controls where only some children are selected.

import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
 
export function IndeterminateCheckbox() {
  return (
    <div className="flex items-center gap-2">
      <Checkbox id="select-all" checked="indeterminate" />
      <Label htmlFor="select-all">Select all items</Label>
    </div>
  );
}

Checked by Default

Use defaultChecked for an uncontrolled checkbox that starts in the checked state.

import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
 
export function DefaultCheckedCheckbox() {
  return (
    <div className="flex items-center gap-2">
      <Checkbox id="newsletter" defaultChecked />
      <Label htmlFor="newsletter">Subscribe to newsletter</Label>
    </div>
  );
}

Disabled

Set disabled to prevent interaction and visually indicate the field is unavailable.

import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
 
export function DisabledCheckbox() {
  return (
    <div className="space-y-3">
      <div className="flex items-center gap-2">
        <Checkbox id="disabled-unchecked" disabled />
        <Label htmlFor="disabled-unchecked" className="text-muted-foreground">
          Disabled unchecked
        </Label>
      </div>
      <div className="flex items-center gap-2">
        <Checkbox id="disabled-checked" disabled defaultChecked />
        <Label htmlFor="disabled-checked" className="text-muted-foreground">
          Disabled checked
        </Label>
      </div>
    </div>
  );
}

Controlled State with useState

Use checked and onCheckedChange together to fully control the checkbox from your component's state.

import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
 
export function ControlledCheckbox() {
  const [checked, setChecked] = useState<boolean | "indeterminate">(false);
 
  return (
    <div className="space-y-4">
      <div className="flex items-center gap-2">
        <Checkbox
          id="controlled"
          checked={checked}
          onCheckedChange={setChecked}
        />
        <Label htmlFor="controlled">Controlled checkbox</Label>
      </div>
      <p className="text-sm text-muted-foreground">
        State: <span className="font-mono">{String(checked)}</span>
      </p>
      <div className="flex gap-2">
        <Button variant="outline" size="sm" onClick={() => setChecked(true)}>
          Check
        </Button>
        <Button variant="outline" size="sm" onClick={() => setChecked(false)}>
          Uncheck
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => setChecked("indeterminate")}
        >
          Indeterminate
        </Button>
      </div>
    </div>
  );
}

As a Form Field in a Group

Render multiple checkboxes together to form a multi-select option group. Each checkbox gets its own id and label. Wrap the group in a <fieldset> with a <legend> for screen reader context.

import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
 
const options = [
  { id: "react", label: "React" },
  { id: "vue", label: "Vue" },
  { id: "svelte", label: "Svelte" },
  { id: "angular", label: "Angular" },
];
 
export function CheckboxGroup() {
  const [selected, setSelected] = useState<string[]>([]);
 
  const toggle = (id: string) =>
    setSelected((prev) =>
      prev.includes(id) ? prev.filter((v) => v !== id) : [...prev, id]
    );
 
  return (
    <fieldset className="space-y-3">
      <legend className="text-sm font-medium">Preferred frameworks</legend>
      {options.map(({ id, label }) => (
        <div key={id} className="flex items-center gap-2">
          <Checkbox
            id={id}
            checked={selected.includes(id)}
            onCheckedChange={() => toggle(id)}
          />
          <Label htmlFor={id}>{label}</Label>
        </div>
      ))}
      {selected.length > 0 && (
        <p className="text-sm text-muted-foreground">
          Selected: {selected.join(", ")}
        </p>
      )}
    </fieldset>
  );
}

API Reference

Checkbox

PropTypeDefaultDescription
checkedboolean | "indeterminate"Controlled checked state. Pass "indeterminate" to show a minus icon.
defaultCheckedbooleanfalseThe default checked state for uncontrolled usage.
onCheckedChange(checked: boolean | "indeterminate") => voidCalled when the checked state changes.
disabledbooleanfalsePrevents interaction and applies disabled styling.
requiredbooleanfalseMarks the checkbox as required in a form context.
idstringThe HTML id used to associate the checkbox with a <Label>.
classNamestringAdditional CSS classes applied to the checkbox root element.
refReact.Ref<HTMLButtonElement>Ref forwarded to the underlying Radix CheckboxPrimitive.Root element.

Accessibility

  • The Checkbox renders as a <button role="checkbox"> under the hood (Radix UI).
  • Always provide a visible label using <Label htmlFor={id}> or an aria-label prop. A checkbox without an accessible name will fail WCAG 1.3.1.
  • The indeterminate state is communicated to assistive technology via aria-checked="mixed".
  • When disabled, the element receives aria-disabled="true" and is removed from the tab order.
  • required sets aria-required="true" so screen readers announce the field as mandatory.
  • Group related checkboxes in a <fieldset> with a <legend> to give the group a descriptive label.
  • The component is fully keyboard operable: Space toggles the checked state, Tab moves focus.

Live View

Open Checkbox in Storybook →