Fiber UI LogoFiberUI

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";