Checkbox
A control that allows the user to toggle between checked and not checked.
Preview
Loading...
import { useState } from "react";
import {
Checkbox,
CheckboxGroup,
CheckboxGroupItem,
} from "@/components/ui/Checkbox";
export function CheckboxDemo() {
return {
const [checked, setChecked] = useState(false);
return (
<div className="flex items-center gap-1">
<Checkbox
id="default"
checked={checked}
onCheckedChange={(checked) => setChecked(checked as boolean)}
/>
<label
htmlFor="default"
className="text-sm font-medium leading-none cursor-pointer"
>
Accept terms and conditions
</label>
</div>
);
};Installation
Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces
pnpm dlx shadcn@latest add @aura/checkboxManual
Install the following dependencies:
pnpm install @radix-ui/react-icons radix-uiCopy and paste the class names utility into your utils/class-names.ts file.
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Copy and paste the Checkbox component into your components/ui/Checkbox.tsx file.
"use client";
/**
* @description A control that allows the user to toggle between checked and not checked.
*/
import * as React from "react";
import { Checkbox as CheckboxRadix } from "radix-ui";
import { CheckIcon } from "@radix-ui/react-icons";
import { cn } from "@/utils/class-names";
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxRadix.Root>) {
return (
<CheckboxRadix.Root
data-slot="checkbox"
className={cn(
"border border-gray-a6 flex size-1.5 items-center justify-center rounded outline-none hover:bg-accent-2 cursor-pointer",
className
)}
{...props}
>
<CheckboxIndicator />
</CheckboxRadix.Root>
);
}
function CheckboxIndicator({
className,
...props
}: React.ComponentProps<typeof CheckboxRadix.Indicator>) {
return (
<CheckboxRadix.Indicator {...props}>
<CheckIcon className={cn(className, "text-accent-9")} />
</CheckboxRadix.Indicator>
);
}
interface CheckboxGroupProps extends React.HTMLAttributes<HTMLDivElement> {
value?: string[];
onValueChange?: (value: string[]) => void;
defaultValue?: string[];
}
function CheckboxGroup({
className,
value,
onValueChange,
defaultValue,
children,
...props
}: CheckboxGroupProps) {
const [internalValue, setInternalValue] = React.useState<string[]>(
defaultValue ?? []
);
const controlledValue = value ?? internalValue;
const handleValueChange = React.useCallback(
(itemValue: string, checked: boolean) => {
const newValue = checked
? [...controlledValue, itemValue]
: controlledValue.filter((v) => v !== itemValue);
if (onValueChange) {
onValueChange(newValue);
} else {
setInternalValue(newValue);
}
},
[controlledValue, onValueChange]
);
return (
<div
data-slot="checkbox-group"
className={cn("flex flex-col gap-1", className)}
{...props}
>
{React.Children.map(children, (child) => {
if (
React.isValidElement<CheckboxGroupItemProps>(child) &&
child.type === CheckboxGroupItem
) {
const childProps = child.props;
return React.cloneElement(child, {
...childProps,
checked: controlledValue.includes(childProps.value),
onCheckedChange: (checked: boolean) =>
handleValueChange(childProps.value, checked),
});
}
return child;
})}
</div>
);
}
interface CheckboxGroupItemProps
extends Omit<React.ComponentProps<typeof CheckboxRadix.Root>, "onCheckedChange"> {
value: string;
label?: React.ReactNode;
description?: React.ReactNode;
onCheckedChange?: (checked: boolean) => void;
}
function CheckboxGroupItem({
className,
value,
label,
description,
id,
...props
}: CheckboxGroupItemProps) {
const checkboxId = id || `checkbox-${value}`;
return (
<div
data-slot="checkbox-group-item"
className={cn("flex items-start gap-1", className)}
>
<Checkbox id={checkboxId} {...props} />
{(label || description) && (
<div className="flex flex-col gap-1">
{label && (
<label
htmlFor={checkboxId}
className="text-sm font-medium leading-none cursor-pointer"
>
{label}
</label>
)}
{description && (
<p className="text-sm text-gray-a11 m-0">{description}</p>
)}
</div>
)}
</div>
);
}
export { Checkbox, CheckboxIndicator, CheckboxGroup, CheckboxGroupItem };Usage
Checked
export const Checked = () => (
<div className="flex items-center gap-1">
<Checkbox id="checked" defaultChecked />
<label
htmlFor="checked"
className="text-sm font-medium leading-none cursor-pointer"
>
I agree to the privacy policy
</label>
</div>
)Unchecked
export const Unchecked = () => (
<div className="flex items-center gap-1">
<Checkbox id="unchecked" />
<label
htmlFor="unchecked"
className="text-sm font-medium leading-none cursor-pointer"
>
Subscribe to newsletter
</label>
</div>
)Disabled
export const Disabled = () => (
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<Checkbox id="disabled-unchecked" disabled />
<label
htmlFor="disabled-unchecked"
className="text-sm font-medium leading-none text-gray-a8 cursor-not-allowed"
>
Disabled unchecked
</label>
</div>
<div className="flex items-center gap-1">
<Checkbox id="disabled-checked" disabled defaultChecked />
<label
htmlFor="disabled-checked"
className="text-sm font-medium leading-none text-gray-a8 cursor-not-allowed"
>
Disabled checked
</label>
</div>
</div>
)WithDescription
export const WithDescription = () => {
const [checked, setChecked] = useState(false);
return (
<div className="flex items-start gap-1">
<Checkbox
id="with-description"
checked={checked}
onCheckedChange={(checked) => setChecked(checked as boolean)}
/>
<div className="flex flex-col gap-1">
<label
htmlFor="with-description"
className="text-sm font-medium leading-none cursor-pointer"
>
Marketing emails
</label>
<p className="text-sm text-gray-a11 m-0">
Receive emails about new products, features, and more.
</p>
</div>
</div>
);
};Group
export const Group = () => {
const [selectedItems, setSelectedItems] = useState<string[]>([]);
const items = [
{
value: "react",
label: "React",
description: "A JavaScript library for building user interfaces",
},
{
value: "typescript",
label: "TypeScript",
description: "JavaScript with syntax for types",
},
{
value: "tailwind",
label: "Tailwind CSS",
description: "A utility-first CSS framework",
},
{
value: "nextjs",
label: "Next.js",
description: "The React Framework for the Web",
},
];
return (
<div className="flex flex-col gap-1">
<div className="text-sm font-semibold">Select your tech stack:</div>
<CheckboxGroup value={selectedItems} onValueChange={setSelectedItems}>
{items.map((item) => (
<CheckboxGroupItem
key={item.value}
value={item.value}
label={item.label}
description={item.description}
/>
))}
</CheckboxGroup>
{selectedItems.length > 0 && (
<div className="mt-2 text-sm text-gray-a11">
Selected: {selectedItems.length} item
{selectedItems.length !== 1 ? "s" : ""}
</div>
)}
</div>
);
};Indeterminate
export const Indeterminate = () => {
const [selectedItems, setSelectedItems] = useState<string[]>(["sub-1"]);
const allItems = ["sub-1", "sub-2", "sub-3"];
const allChecked = allItems.every((item) => selectedItems.includes(item));
const someChecked = selectedItems.length > 0 && !allChecked;
const handleParentChange = (checked: boolean) => {
setSelectedItems(checked ? allItems : []);
};
const handleChildChange = (itemId: string, checked: boolean) => {
setSelectedItems((prev) =>
checked ? [...prev, itemId] : prev.filter((id) => id !== itemId)
);
};
return (
<div className="flex flex-col gap-1.5">
<div className="flex items-center gap-1">
<Checkbox
id="parent"
checked={allChecked ? true : someChecked ? "indeterminate" : false}
onCheckedChange={(checked) => handleParentChange(checked as boolean)}
/>
<label
htmlFor="parent"
className="text-sm font-semibold leading-none cursor-pointer"
>
Select all notifications
</label>
</div>
<div className="ml-1.5 flex flex-col gap-1 border-l-2 border-gray-a6 pl-1.5">
{allItems.map((item, index) => (
<div key={item} className="flex items-center gap-1">
<Checkbox
id={item}
checked={selectedItems.includes(item)}
onCheckedChange={(checked) =>
handleChildChange(item, checked as boolean)
}
/>
<label
htmlFor={item}
className="text-sm font-medium leading-none cursor-pointer"
>
Notification {index + 1}
</label>
</div>
))}
</div>
</div>
);
};