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 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-primary has-aria-checked:bg-primary/20 dark:has-aria-checked:border-primary flex items-start gap-3 rounded-lg border p-3"
            >
                <Checkbox id="toggle-2" defaultSelected className="" />
                <div className="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>
    );
};

Indeterminate

Indeterminate checkbox
"use client";

import { useState } from "react";
import { Checkbox } from "@repo/ui/components/checkbox";
import { Button } from "@repo/ui/components/button";

export const Example4 = () => {
    const [selected, setSelected] = useState<boolean | "indeterminate">(
        "indeterminate",
    );

    return (
        <div className="space-y-4">
            <div className="flex items-center gap-2">
                <Checkbox
                    isIndeterminate={selected === "indeterminate"}
                    isSelected={selected === true}
                    onChange={(isSelected) => setSelected(isSelected)}
                />
                Indeterminate checkbox
            </div>

            <div className="flex gap-2">
                <Button
                    onPress={() => setSelected("indeterminate")}
                    variant="outline"
                >
                    Set Indeterminate
                </Button>
                <Button onPress={() => setSelected(true)} variant="outline">
                    Set Checked
                </Button>
                <Button onPress={() => setSelected(false)} variant="outline">
                    Set Unchecked
                </Button>
            </div>
        </div>
    );
};

Component Code

"use client";

import { Check, Minus } from "lucide-react";
import {
    Checkbox as AriaCheckbox,
    CheckboxProps as AriaCheckboxProps,
    composeRenderProps,
} from "react-aria-components";
import { tv, type VariantProps, cn } from "tailwind-variants";

const checkboxVariants = tv({
    base: "ring-offset-background group-data-focus-visible:ring-ring border-primary group-data-focus-visible:outline-none group-data-focus-visible:ring-2 group-data-focus-visible:ring-offset-2 flex h-5 w-5 shrink-0 items-center justify-center overflow-hidden rounded-md border",
    variants: {
        size: {
            default: "h-5 w-5",
            sm: "h-4 w-4",
            lg: "h-6 w-6",
        },
    },
    defaultVariants: {
        size: "default",
    },
});

const checkboxIconVariants = tv({
    base: "animate-in fade-in zoom-in transition-all duration-200 ease-out",
    variants: {
        size: {
            default: "h-4 w-4",
            sm: "h-3 w-3",
            lg: "h-5 w-5",
        },
    },
    defaultVariants: {
        size: "default",
    },
});

export interface CheckboxProps
    extends Omit<AriaCheckboxProps, "children">,
        VariantProps<typeof checkboxVariants> {
    className?: string;
}

export const Checkbox: React.FC<CheckboxProps> = (props) => {
    const { className, size, ...restProps } = props;
    return (
        <AriaCheckbox
            {...restProps}
            className={composeRenderProps(
                className,
                (className, { isDisabled }) =>
                    cn(
                        "group flex cursor-pointer items-center gap-2 text-sm [-webkit-tap-highlight-color:transparent]",
                        isDisabled && "cursor-not-allowed opacity-50",
                        className,
                    ) || "",
            )}
        >
            {({
                isHovered,
                isPressed,
                isDisabled,
                isSelected,
                isIndeterminate,
            }) => (
                <span
                    className={cn(
                        checkboxVariants({ size }),
                        (isSelected || isIndeterminate) &&
                            "bg-primary text-primary-foreground",
                    )}
                    data-hovered={isHovered}
                    data-pressed={isPressed}
                    data-disabled={isDisabled}
                    data-state={isSelected ? "checked" : null}
                    aria-checked={isSelected}
                    aria-hidden="true"
                >
                    {isIndeterminate ? (
                        <Minus className={checkboxIconVariants({ size })} />
                    ) : isSelected ? (
                        <Check className={checkboxIconVariants({ size })} />
                    ) : null}
                </span>
            )}
        </AriaCheckbox>
    );
};

Checkbox.displayName = "Checkbox";