Build websites 10x faster with HextaUI Blocks — Learn more
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.

components/ui/checkbox.tsx
"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

Preferences

Receive email updates about your account

Receive text message alerts

Receive promotional emails and offers

Privacy

Others can view your profile information

Others can see when you're online

<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

PropTypeDefault
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