Inputs & Forms

Checkbox

A Radix Checkbox whose check draws in via a stroke-dashoffset transition — rapid toggling retargets mid-draw — with an indeterminate state.

Install

npx shadcn@latest add @paragon/checkbox

checkbox.tsx

"use client";

import * as React from "react";
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { cn } from "@/lib/utils";

export interface CheckboxProps
  extends React.ComponentProps<typeof CheckboxPrimitive.Root> {}

/**
 * Radix Checkbox with a path-draw check. The check is a permanently mounted
 * SVG path whose stroke-dashoffset transitions between 1 and 0, so rapid
 * toggling retargets mid-draw instead of restarting. Supports the
 * `indeterminate` checked state (drawn dash).
 */
export function Checkbox({ className, ...props }: CheckboxProps) {
  return (
    <CheckboxPrimitive.Root
      data-slot="checkbox"
      className={cn(
        "group peer relative size-4 shrink-0 rounded-[5px] border border-input bg-transparent",
        "transition-[background-color,border-color,box-shadow] duration-150 ease-out",
        "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
        "data-[state=checked]:border-primary data-[state=checked]:bg-primary",
        "data-[state=indeterminate]:border-primary data-[state=indeterminate]:bg-primary",
        "disabled:cursor-not-allowed disabled:opacity-50",
        // Extend the hit area to 40px without growing the visual.
        "after:absolute after:top-1/2 after:left-1/2 after:size-10 after:-translate-x-1/2 after:-translate-y-1/2",
        className,
      )}
      {...props}
    >
      <span
        aria-hidden
        className="pointer-events-none absolute inset-0 flex items-center justify-center text-primary-foreground"
      >
        <svg
          viewBox="0 0 12 12"
          fill="none"
          stroke="currentColor"
          strokeWidth={1.75}
          strokeLinecap="round"
          strokeLinejoin="round"
          className="size-3"
        >
          {/* Check — drawn when checked. */}
          <path
            d="M2.25 6.4 4.9 9 9.75 3.5"
            pathLength={1}
            className={cn(
              "[stroke-dasharray:1] [stroke-dashoffset:1]",
              "transition-[stroke-dashoffset] duration-200 ease-out motion-reduce:transition-none",
              "group-data-[state=checked]:[stroke-dashoffset:0]",
            )}
          />
          {/* Dash — drawn when indeterminate. */}
          <path
            d="M2.75 6h6.5"
            pathLength={1}
            className={cn(
              "[stroke-dasharray:1] [stroke-dashoffset:1]",
              "transition-[stroke-dashoffset] duration-200 ease-out motion-reduce:transition-none",
              "group-data-[state=indeterminate]:[stroke-dashoffset:0]",
            )}
          />
        </svg>
      </span>
    </CheckboxPrimitive.Root>
  );
}