Checkbox
A checkbox built on radix-ui with 4 tones and 3 sizes. The tone sets the checked fill color.
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
export default function CheckboxDemo() {
return (
<div className="flex flex-col gap-3">
<Label>
<Checkbox tone="brand" defaultChecked /> Email me deploy alerts
</Label>
<Label>
<Checkbox /> Subscribe to changelog
</Label>
</div>
)
}Installation
Run the following command
npx shadcn@latest add @ai2/checkboxDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install class-variance-authority@^0.7.1 radix-ui@^1.6.1 lucide-react@^1.23.0Add the cn util
lib/utils.tsimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/* Adds a source-attribution ref param to a URL (the inspiration exports mark their
outbound links with an ai2.design attribution). An invalid URL is returned as is.
This file is SHOWN TO THE CONSUMER: the docs component pages render the source of
`cn` in a code block, so a Turkish comment here would reach every one of those
pages. Keep it English. */
export function withRef(url: string, ref = "ai2.design"): string {
try {
const u = new URL(url)
u.searchParams.set("ref", ref)
return u.toString()
} catch {
return url
}
}Copy the source code
components/ui/checkbox.tsx"use client"
import type * as React from "react"
import { Check } from "lucide-react"
import { Checkbox as CheckboxPrimitive } from "@/components/ui/primitives"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const checkboxVariants = cva(
"peer relative shrink-0 rounded-[0.28rem] border border-field-border shadow-xs outline-none transition-colors duration-(--motion-fast) after:absolute after:-inset-1.5 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40 dark:bg-input/30",
{
variants: {
tone: {
neutral: "data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
brand: "data-[state=checked]:border-brand data-[state=checked]:bg-brand data-[state=checked]:text-brand-foreground",
success: "data-[state=checked]:border-success data-[state=checked]:bg-success data-[state=checked]:text-success-foreground",
danger: "data-[state=checked]:border-danger data-[state=checked]:bg-danger data-[state=checked]:text-danger-foreground",
},
size: {
sm: "size-3.5",
md: "size-4",
lg: "size-5",
},
},
defaultVariants: { tone: "neutral", size: "md" },
}
)
interface CheckboxProps
extends React.ComponentProps<typeof CheckboxPrimitive.Root>,
VariantProps<typeof checkboxVariants> {}
function Checkbox({ className, tone, size, ...props }: CheckboxProps) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
data-tone={tone ?? "neutral"}
className={cn(checkboxVariants({ tone, size, className }))}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="flex items-center justify-center text-current"
>
<Check className="size-[0.85em] stroke-[3]" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox, checkboxVariants, type CheckboxProps }Manual installs skip the @ai2/tokens theme, so add the token CSS from the theming guide or the tone colors will be missing.
Usage
import { Checkbox } from "@/components/ui/checkbox"
<Checkbox tone="brand" defaultChecked />Both axes are optional. Omit them and you get the neutral medium checkbox. Wrap it in a Label for a clickable row.
Examples
Tones
The tone only affects the checked state. Unchecked boxes share the same border regardless of tone.
Sizes
With label
Disabled and invalid
Set aria-invalid on validation errors. The danger border and ring style is built in.
Props
Checkbox also accepts every radix Checkbox prop, such as checked, defaultChecked, onCheckedChange and disabled.
| Prop | Type | Default | Description |
|---|---|---|---|
tone | "neutral" | "brand" | "success" | "danger" | "neutral" | Fill color of the checked state, mapped to theme tokens. |
size | "sm" | "md" | "lg" | "md" | Box dimensions; the check icon scales with the box. |
ai2 Checkbox: a toned, token-driven checkbox for React
The ai2 Checkbox is a shadcn-compatible checkbox component for React, built on the radix-ui Checkbox primitive and styled with Tailwind CSS v4. Where most checkboxes only come in one color, this one carries the ai2 tone axis: neutral, brand, success and danger checked fills, all mapped to theme tokens, plus three sizes for dense tables through touch-friendly forms.
Because it ships through the shadcn registry format, you install it with one CLI command, an MCP agent, or a copy-paste, and the source lands in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.
What is the ai2 Checkbox?
It is a single Checkbox component wrapping the radix root and indicator, exposing two cva axes: tone (neutral, brand, success, danger) and size (sm, md, lg). The anatomy matches shadcn/ui, so existing snippets and AI agents keep working; the axes are additive.
The tone only colors the checked state, so a mixed group of tones still lines up cleanly when unchecked. The check icon is sized in em units and scales with the box, and the color transition runs on the shared ai2 motion tokens instead of hardcoded durations.
Why use it
- Accessible by construction: The radix-ui Checkbox primitive renders a real button with role checkbox, manages aria-checked, supports form submission via a hidden input, and toggles with Space.
- Four semantic tones: neutral for everyday forms, brand for primary opt-ins, success for confirmations, danger for destructive acknowledgements like "I understand this cannot be undone".
- Three sizes: sm for dense tables and trees, md as the default, lg for touch-first and marketing forms. The check icon scales with the box automatically.
- Validation styling built in: Set aria-invalid and the checkbox switches to the danger border and ring without extra classes, matching inputs across the system.
- Agent-readable metadata: The registry item states the tone and size matrix in plain words, so an MCP agent can find, inspect and install it without guessing.
Features
- shadcn registry install: One command adds the component, its dependencies and the @ai2/tokens theme to your project.
- Token-mapped checked fills: Each tone reads its color from the theme tokens (primary, brand, success, danger), so the checkbox follows your palette in light and dark mode.
- Motion from tokens: The check transition uses --motion-fast from the shared token file, matching every other ai2 control.
- Focus-visible ring: Keyboard focus shows a 3px ring on the ring token; mouse clicks stay clean.
- Peer class for label styling: The root carries the peer class, so a sibling label can restyle itself from the checkbox state with peer-disabled or peer-checked utilities.
- Data attributes for styling: data-slot, data-tone and radix data-state land on the element, so CSS can target any tone or state without forking the component.
Production tips
- Always pair with a label: Wrap the checkbox in an ai2 Label or link it with htmlFor. A bare box has no accessible name and a tiny hit target.
- Use tones with intent: Tones are semantic, not decorative. A danger checkbox next to a destructive action reads as a warning; scattering tones randomly dilutes that signal.
- Prefer onCheckedChange: The radix change callback is onCheckedChange, not onChange. Wire controlled state through it and the checked prop.
- Surface errors with aria-invalid: For required consents, set aria-invalid when validation fails and describe the error with aria-describedby so screen readers announce it.
- Mind the size in dense UIs: size="sm" keeps table rows tight, but keep the clickable label generous so the effective touch target stays usable.
Works with the rest of ai2
The checkbox slots straight into form composition. Wrap it in an ai2 Label for a clickable row, or mount it inside an ai2 Field to pick up description and error wiring. For row selection, pair it with the ai2 Table header and body cells.
When the choice is on-off rather than multi-select, reach for the ai2 Switch, which shares the same tone axis; for mutually exclusive options use the ai2 Radio Group. All three read one token source, so mixed forms stay consistent.