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-checkboxUsage
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
| Prop | Type | Default | Description |
|---|---|---|---|
checked | boolean | "indeterminate" | — | Controlled checked state. Pass "indeterminate" to show a minus icon. |
defaultChecked | boolean | false | The default checked state for uncontrolled usage. |
onCheckedChange | (checked: boolean | "indeterminate") => void | — | Called when the checked state changes. |
disabled | boolean | false | Prevents interaction and applies disabled styling. |
required | boolean | false | Marks the checkbox as required in a form context. |
id | string | — | The HTML id used to associate the checkbox with a <Label>. |
className | string | — | Additional CSS classes applied to the checkbox root element. |
ref | React.Ref<HTMLButtonElement> | — | Ref forwarded to the underlying Radix CheckboxPrimitive.Root element. |
Accessibility
- The
Checkboxrenders as a<button role="checkbox">under the hood (Radix UI). - Always provide a visible label using
<Label htmlFor={id}>or anaria-labelprop. A checkbox without an accessible name will fail WCAG 1.3.1. - The
indeterminatestate is communicated to assistive technology viaaria-checked="mixed". - When
disabled, the element receivesaria-disabled="true"and is removed from the tab order. requiredsetsaria-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:
Spacetoggles the checked state,Tabmoves focus.