A Radix checkbox with checked and indeterminate (mixed) states, a check or minus icon, focus ring and invalid styling, for forms and select-all lists.
You agree to our Terms of Service and Privacy Policy.
import { Checkbox } from "@/components/ballmac/checkbox"
import { Label } from "@/components/ballmac/label"
export default function CheckboxDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-5">
<div className="flex items-start gap-3">
<Checkbox id="terms" defaultChecked aria-describedby="terms-description" />
<div className="grid gap-1.5">
<Label htmlFor="terms">Accept terms and conditions</Label>
<p id="terms-description" className="text-sm text-muted-foreground">
You agree to our Terms of Service and Privacy Policy.
</p>
</div>
</div>
<div className="flex items-center gap-3">
<Checkbox id="updates" />
<Label htmlFor="updates">Email me product updates</Label>
</div>
<div className="flex items-center gap-3">
<Checkbox id="beta" disabled />
<Label htmlFor="beta">Join the beta (invite only)</Label>
</div>
</div>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/checkboxInstall the dependencies.
$ pnpm add radix-ui lucide-reactCopy the source into your project.
components/ballmac/checkbox.tsx// Ballmac UI: Checkbox. https://ui.ballmac.com/components/checkbox // Based on shadcn/ui's Checkbox (MIT, Copyright (c) 2023 shadcn), restyled with an indeterminate state. "use client" import * as React from "react" import { CheckIcon, MinusIcon } from "lucide-react" import { Checkbox as CheckboxPrimitive } from "radix-ui" import { cn } from "@/lib/utils" type CheckboxProps = React.ComponentProps<typeof CheckboxPrimitive.Root> function Checkbox({ className, ...props }: CheckboxProps) { return ( <CheckboxPrimitive.Root data-slot="checkbox" className={cn( "peer relative size-4 shrink-0 rounded-[4px] border border-input bg-background shadow-xs outline-none transition-[color,background-color,border-color,box-shadow] duration-150 dark:bg-input/30", "after:absolute after:-inset-2 after:content-['']", "hover:border-foreground/30 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", "disabled:cursor-not-allowed disabled:opacity-50", "aria-invalid:border-destructive aria-invalid:ring-destructive/20", "data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", "data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground", className )} {...props} > <CheckboxPrimitive.Indicator data-slot="checkbox-indicator" className="group/indicator flex items-center justify-center text-current transition-none" > <CheckIcon className="size-3.5 group-data-[state=indeterminate]/indicator:hidden" strokeWidth={3} aria-hidden="true" /> <MinusIcon className="hidden size-3.5 group-data-[state=indeterminate]/indicator:block" strokeWidth={3} aria-hidden="true" /> </CheckboxPrimitive.Indicator> </CheckboxPrimitive.Root> ) } export { Checkbox, type CheckboxProps }Update the import paths to match your project setup.
Usage
import { Checkbox } from "@/components/ballmac/checkbox"The full example is in the Code tab above.
Examples
Select all
"use client"
import * as React from "react"
import { Checkbox } from "@/components/ballmac/checkbox"
import { Label } from "@/components/ballmac/label"
const events = [
{ id: "deploy", label: "Deployment finished" },
{ id: "invoice", label: "Invoice paid" },
{ id: "comment", label: "New comment" },
{ id: "member", label: "Member joined" },
]
export default function CheckboxIndeterminate() {
const [selected, setSelected] = React.useState<string[]>(["deploy", "comment"])
const all = selected.length === events.length
const state = all ? true : selected.length > 0 ? "indeterminate" : false
return (
<fieldset className="w-full max-w-xs rounded-xl border bg-card p-4">
<legend className="sr-only">Notify me about</legend>
<div className="flex items-center gap-3 border-b pb-3">
<Checkbox
id="events-all"
checked={state}
onCheckedChange={(checked) => setSelected(checked === true ? events.map((e) => e.id) : [])}
/>
<Label htmlFor="events-all">All events</Label>
<span className="ms-auto text-xs text-muted-foreground tabular-nums">
{selected.length}/{events.length}
</span>
</div>
<div className="flex flex-col gap-3 pt-3 ps-7">
{events.map((event) => (
<div key={event.id} className="flex items-center gap-3">
<Checkbox
id={`events-${event.id}`}
checked={selected.includes(event.id)}
onCheckedChange={(checked) =>
setSelected((prev) => (checked === true ? [...prev, event.id] : prev.filter((id) => id !== event.id)))
}
/>
<Label htmlFor={`events-${event.id}`} className="font-normal">
{event.label}
</Label>
</div>
))}
</div>
</fieldset>
)
}Accessibility
| Key | Action |
|---|---|
| Space | Toggles the checkbox |
| Tab | Moves focus to the checkbox |
Use with AI
Binary or tri-state choice. Pass checked="indeterminate" for a parent row whose children are partly selected; aria-checked becomes "mixed". Pair with Label via id/htmlFor. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Checkbox (@ballmac/checkbox) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Agreeing to terms or opting into emails
- Multi-select lists and table row selection
- A select-all control that reflects partial selection
Not for
- Settings that apply immediately (use switch)
- Choosing exactly one option from several (use a radio group or select)
Registry JSON: https://ui.ballmac.com/r/checkbox.json
Credits
Based on shadcn/ui Checkbox, adapted by Ballmac. Free to use in personal and commercial projects.
- Registry
- shadcn/utils
Pairs well with
Label
An accessible form label built on Radix Label that dims itself when the paired peer control is disabled and never selects text on double-click.
Button
A button with six variants, three sizes, a pill shape and a built-in loading state. buttonVariants() styles links the same way.
Accordion
Vertically stacked disclosure sections on Radix Accordion, single or multiple open, with hairline dividers, a rotating plus and height animation.
Avatar
A Radix avatar with image and initials fallback, three sizes, an optional presence dot, and AvatarGroup for overlapping stacks with a +N overflow counter.