Checkbox
The checkbox component
Basic Usage
By clicking this checkbox, you agree to the terms and conditions.
import { Checkbox } from "@repo/ui/components/checkbox";
import { Label } from "@repo/ui/components/label";
export const Example1 = () => {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-3">
<Checkbox id="terms" />
<Label htmlFor="terms">Accept terms and conditions</Label>
</div>
<div className="flex items-start gap-3">
<Checkbox id="terms-2" defaultSelected />
<div className="space-y-2">
<Label htmlFor="terms-2">Accept terms and conditions</Label>
<p className="text-muted-foreground not-prose text-sm">
By clicking this checkbox, you agree to the terms and
conditions.
</p>
</div>
</div>
<div className="group-[data] flex items-start gap-3">
<Checkbox id="toggle" isDisabled />
<Label htmlFor="toggle">Enable notifications</Label>
</div>
<Label
htmlFor="toggle-2"
className="hover:bg-accent/50 has-aria-checked:border-blue-600 has-aria-checked:bg-blue-50 dark:has-aria-checked:border-blue-900 dark:has-aria-checked:bg-blue-950 flex items-start gap-3 rounded-lg border p-3"
>
<Checkbox
id="toggle-2"
defaultSelected
className="data-[state=checked]:border-blue-600 data-[state=checked]:bg-blue-600 data-[state=checked]:text-white dark:data-[state=checked]:border-blue-700 dark:data-[state=checked]:bg-blue-700"
/>
<div className="not-prose grid gap-1.5 font-normal">
<p className="text-sm font-medium leading-none">
Enable notifications
</p>
<p className="text-muted-foreground text-sm">
You can enable or disable notifications at any time.
</p>
</div>
</Label>
</div>
);
};
Controlled
Controlled checkbox
Checked: No
"use client";
import { useState } from "react";
import { Checkbox } from "@repo/ui/components/checkbox";
export const Example2 = () => {
const [checked, setChecked] = useState(false);
return (
<div className="space-x-2">
<div className="flex items-center gap-2">
<Checkbox isSelected={checked} onChange={setChecked} />
Controlled checkbox
</div>
<p>Checked: {checked ? "Yes" : "No"}</p>
</div>
);
};
Disabled
Disabled unchecked
Disabled checked
import { Checkbox } from "@repo/ui/components/checkbox";
export const Example3 = () => {
return (
<div className="flex flex-col space-y-2">
<div className="flex items-center gap-2">
<Checkbox isDisabled />
Disabled unchecked
</div>
<div className="flex items-center gap-2">
<Checkbox isDisabled isSelected />
Disabled checked
</div>
</div>
);
};
Component Code
"use client";
import { cn } from "@repo/ui/lib/utils";
import { forwardRef, useRef } from "react";
import { AriaCheckboxProps, useCheckbox, useFocusRing } from "react-aria";
import { useToggleState } from "react-stately";
import { Check } from "lucide-react";
// type CheckboxComponentProps = ComponentProps<"input">;
// interface CheckboxProps
// extends CheckboxComponentProps,
// Omit<AriaCheckboxProps, keyof CheckboxComponentProps> {
// className?: string;
// }
interface CheckboxProps extends AriaCheckboxProps {
className?: string;
}
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
({ className, ...restProps }, forwardedRef) => {
const localRef = useRef<HTMLInputElement>(null);
const mergedRef = (node: HTMLInputElement | null) => {
if (!node) return;
localRef.current = node;
if (typeof forwardedRef === "function") {
forwardedRef(node);
} else if (forwardedRef) {
forwardedRef.current = node;
}
};
const state = useToggleState(restProps as AriaCheckboxProps);
const { inputProps, isSelected, isDisabled, labelProps } = useCheckbox(
restProps as AriaCheckboxProps,
state,
localRef,
);
const { focusProps, isFocusVisible, isFocused } = useFocusRing();
return (
<>
<input
role="checkbox"
{...inputProps}
{...focusProps}
ref={mergedRef}
type="checkbox"
className="peer sr-only"
aria-checked={isSelected}
data-disabled={isDisabled || undefined}
disabled={isDisabled} // Add this
/>
<label
htmlFor={restProps.id}
{...labelProps}
className={cn(
"peer inline-flex items-center gap-2",
"border-primary ring-offset-background h-4 w-4 shrink-0 rounded-sm border",
"flex items-center justify-center overflow-hidden",
(isFocusVisible || isFocused) &&
"ring-ring outline-none ring-2 ring-offset-2",
isSelected && "bg-primary text-primary-foreground",
isDisabled && "cursor-not-allowed opacity-50",
className,
)}
data-disabled={isDisabled || undefined}
aria-disabled={isDisabled || undefined}
aria-hidden="true" // Decorative, input handles semantics
data-state={isSelected ? "checked" : null}
>
{isSelected && <Check className="h-4 w-4" />}
</label>
</>
);
},
);
Checkbox.displayName = "Checkbox";