UI/UI
Checkbox
A customizable checkbox component with smooth animations and multiple states.
<div className="flex flex-col gap-4">
<Checkbox label="Default checkbox" />
<Checkbox label="Checked checkbox" checked />
<Checkbox label="Indeterminate checkbox" checked="indeterminate" />
<Checkbox label="Disabled checkbox" disabled />
<Checkbox label="Disabled checked" checked disabled />
</div>
Installation
Install following dependencies:
npm install @radix-ui/react-checkbox class-variance-authority lucide-react motion
pnpm add @radix-ui/react-checkbox class-variance-authority lucide-react motion
yarn add @radix-ui/react-checkbox class-variance-authority lucide-react motion
bun add @radix-ui/react-checkbox class-variance-authority lucide-react motion
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { motion, AnimatePresence } from "motion/react";
const checkboxVariants = cva(
"peer shrink-0 rounded-sm border border-[hsl(var(--hu-border))] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[hsl(var(--hu-ring))] disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[hsl(var(--hu-primary))] data-[state=checked]:text-[hsl(var(--hu-primary-foreground))] data-[state=checked]:border-[hsl(var(--hu-primary))] data-[state=indeterminate]:bg-[hsl(var(--hu-primary))] data-[state=indeterminate]:text-[hsl(var(--hu-primary-foreground))] data-[state=indeterminate]:border-[hsl(var(--hu-primary))] bg-[hsl(var(--hu-accent))] text-[hsl(var(--hu-foreground))] focus-visible:ring-offset-[hsl(var(--hu-background))] focus-visible:ring-offset-2 transition-colors",
{
variants: {
size: {
sm: "h-3 w-3",
default: "h-4 w-4",
lg: "h-5 w-5",
},
},
defaultVariants: {
size: "default",
},
},
);
interface CheckboxProps
extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>,
VariantProps<typeof checkboxVariants> {
label?: string;
description?: string;
error?: string;
}
const CheckboxRoot = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
CheckboxProps
>(({ className, size, label, description, error, id, ...props }, ref) => {
const checkboxId = id || React.useId();
const iconSize = size === "sm" ? 10 : size === "lg" ? 14 : 12;
// Custom SVG check path for drawing animation
const checkPath = "M3 6l3 3 6-6";
const minusPath = "M3 6h8";
return (
<div className="flex flex-col gap-1">
<div className="flex items-start gap-2">
<CheckboxPrimitive.Root
ref={ref}
id={checkboxId}
className={cn(checkboxVariants({ size }), className)}
{...props}
>
<CheckboxPrimitive.Indicator asChild>
<div className="flex items-center justify-center text-current">
<AnimatePresence mode="wait">
{props.checked === "indeterminate" ? (
<motion.svg
key="indeterminate"
width={iconSize}
height={iconSize}
viewBox="0 0 14 14"
fill="none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.1 }}
>
<motion.path
d={minusPath}
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
/>
</motion.svg>
) : (
<motion.svg
key="check"
width={iconSize}
height={iconSize}
viewBox="0 0 14 14"
fill="none"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.1 }}
>
<motion.path
d={checkPath}
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{
duration: 0.3,
ease: "easeInOut",
delay: 0.1,
}}
/>
</motion.svg>
)}
</AnimatePresence>
</div>
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
{(label || description) && (
<div className="grid gap-1.5 leading-none">
{label && (
<label
htmlFor={checkboxId}
className={cn(
"text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer",
)}
>
{label}
</label>
)}
{description && (
<p className="text-xs text-[hsl(var(--hu-muted-foreground))] peer-disabled:opacity-70">
{description}
</p>
)}
</div>
)}
</div>
{error && (
<p className="text-xs text-[hsl(var(--hu-destructive))] ml-6">
{error}
</p>
)}
</div>
);
});
CheckboxRoot.displayName = "Checkbox";
// Simple wrapper that maintains the same API
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
CheckboxProps
>((props, ref) => <CheckboxRoot ref={ref} {...props} />);
Checkbox.displayName = "Checkbox";
export { Checkbox, checkboxVariants, type CheckboxProps };
npx hextaui@latest add checkbox
pnpm dlx hextaui@latest add checkbox
yarn dlx hextaui@latest add checkbox
bun x hextaui@latest add checkbox
Usage
import { Checkbox } from "@/components/ui/Checkbox";
<Checkbox label="Accept terms" />
<Checkbox label="Subscribe" checked />
<Checkbox label="Select all" checked="indeterminate" />
Examples
Basic Usage
function CheckboxBasic() {
const [checked, setChecked] = useState(false);
return (
<div className="flex flex-col gap-4">
<Checkbox
label="Accept terms and conditions"
checked={checked}
onCheckedChange={(checked) => setChecked(!!checked)}
/>
<Checkbox label="Subscribe to newsletter" />
<Checkbox label="This checkbox is disabled" disabled />
<Checkbox label="Remember me" checked disabled />
</div>
);
}
Sizes
<div className="flex flex-col gap-4">
<Checkbox size="sm" label="Small checkbox" />
<Checkbox size="default" label="Default checkbox" />
<Checkbox size="lg" label="Large checkbox" />
</div>
With Description
Receive emails about new products, features, and more.
Receive emails about your account security.
Receive emails for friend requests, follows, and more.
<div className="flex flex-col gap-4">
<Checkbox
label="Marketing emails"
description="Receive emails about new products, features, and more."
/>
<Checkbox
label="Security emails"
description="Receive emails about your account security."
checked
/>
<Checkbox
label="Social emails"
description="Receive emails for friend requests, follows, and more."
disabled
/>
</div>
Indeterminate State
function CheckboxIndeterminate() {
const [items, setItems] = useState([
{ id: 1, label: "Item 1", checked: false },
{ id: 2, label: "Item 2", checked: true },
{ id: 3, label: "Item 3", checked: false },
]);
const checkedItems = items.filter((item) => item.checked);
const isIndeterminate = checkedItems.length > 0 && checkedItems.length < items.length;
const isAllChecked = checkedItems.length === items.length;
const handleSelectAll = (checked: boolean | "indeterminate") => {
if (checked === "indeterminate") return;
setItems(items.map((item) => ({ ...item, checked })));
};
const handleItemChange = (id: number, checked: boolean) => {
setItems(items.map((item) =>
item.id === id ? { ...item, checked } : item
));
};
return (
<div className="flex flex-col gap-3">
<Checkbox
label="Select all"
checked={isAllChecked}
indeterminate={isIndeterminate}
onCheckedChange={handleSelectAll}
/>
<div className="ml-6 flex flex-col gap-2">
{items.map((item) => (
<Checkbox
key={item.id}
label={item.label}
checked={item.checked}
onCheckedChange={(checked) => handleItemChange(item.id, !!checked)}
/>
))}
</div>
</div>
);
}
With Error State
function CheckboxWithError() {
const [agreed, setAgreed] = useState(false);
const [attempted, setAttempted] = useState(false);
const handleSubmit = () => {
setAttempted(true);
if (agreed) {
alert("Form submitted!");
setAttempted(false);
}
};
return (
<div className="flex flex-col gap-4">
<Checkbox
label="I agree to the terms and conditions"
checked={agreed}
onCheckedChange={(checked) => setAgreed(!!checked)}
error={attempted && !agreed ? "You must agree to the terms" : undefined}
/>
<Button
onClick={handleSubmit}
className="px-4 py-2 bg-[hsl(var(--hu-primary))] text-[hsl(var(--hu-primary-foreground))] rounded-md hover:bg-[hsl(var(--hu-primary))]/80 transition-colors"
>
Submit
</Button>
</div>
);
}
Checkbox Group
Select your skills:
function CheckboxGroup() {
const [selectedSkills, setSelectedSkills] = useState<string[]>([]);
const skills = [
{ id: "react", label: "React" },
{ id: "vue", label: "Vue.js" },
{ id: "angular", label: "Angular" },
{ id: "svelte", label: "Svelte" },
{ id: "nextjs", label: "Next.js" },
];
const handleSkillChange = (skillId: string, checked: boolean) => {
if (checked) {
setSelectedSkills([...selectedSkills, skillId]);
} else {
setSelectedSkills(selectedSkills.filter(id => id !== skillId));
}
};
return (
<div className="flex flex-col gap-3">
<h3 className="text-sm font-medium">Select your skills:</h3>
<div className="flex flex-col gap-2">
{skills.map((skill) => (
<Checkbox
key={skill.id}
label={skill.label}
checked={selectedSkills.includes(skill.id)}
onCheckedChange={(checked) => handleSkillChange(skill.id, !!checked)}
/>
))}
</div>
{selectedSkills.length > 0 && (
<p className="text-xs text-[hsl(var(--hu-muted-foreground))]">
Selected: {selectedSkills.join(", ")}
</p>
)}
</div>
);
}
Animated Checkboxes
Watch the smooth animation when toggled
Watch the smooth animation when toggled
Watch the smooth animation when toggled
function CheckboxAnimated() {
const [items, setItems] = useState([
{ id: 1, label: "Animated checkbox 1", checked: false },
{ id: 2, label: "Animated checkbox 2", checked: false },
{ id: 3, label: "Animated checkbox 3", checked: false },
]);
const handleChange = (id: number, checked: boolean) => {
setItems(items.map(item =>
item.id === id ? { ...item, checked } : item
));
};
return (
<div className="flex flex-col gap-3">
{items.map((item) => (
<Checkbox
key={item.id}
label={item.label}
description="Watch the smooth animation when toggled"
checked={item.checked}
onCheckedChange={(checked) => handleChange(item.id, !!checked)}
variant="default"
/>
))}
</div>
);
}
Form Integration
<form className="space-y-4">
<div className="space-y-3">
<h3 className="text-sm font-medium">Preferences</h3>
<Checkbox
label="Email notifications"
description="Receive email updates about your account"
/>
<Checkbox
label="SMS notifications"
description="Receive text message alerts"
/>
<Checkbox
label="Marketing communications"
description="Receive promotional emails and offers"
/>
</div>
<div className="space-y-3">
<h3 className="text-sm font-medium">Privacy</h3>
<Checkbox
label="Make profile public"
description="Others can view your profile information"
/>
<Checkbox
label="Show activity status"
description="Others can see when you're online"
/>
</div>
</form>
Props
Prop | Type | Default |
---|---|---|
className? | string | undefined |
required? | boolean | false |
value? | string | on |
name? | string | undefined |
disabled? | boolean | false |
onCheckedChange? | (checked: boolean | "indeterminate") => void | undefined |
checked? | boolean | "indeterminate" | undefined |
error? | string | undefined |
description? | string | undefined |
label? | string | undefined |
size? | "sm" | "default" | "lg" | "default" |
variant? | "default" | "secondary" | "destructive" | "outline" | "ghost" | "default" |
Edit on GitHub
Last updated on