Skip to content
WANCRAFT

F.114Forms & Inputs

Animated Checkbox

A real checkbox with a drawn check, an indeterminate dash morph, a spring press and a tri-state select-all group.

NewReactMotionSVGTailwindintermediate

Overview

Animated Checkbox keeps a native <input type="checkbox"> underneath — visually hidden but laid over the box — so clicking, Space, labels, forms and screen readers all work natively. The check is drawn with an animated stroke (pathLength drives stroke-dashoffset), morphs into a dash for the indeterminate state, and the box answers a press with a small spring and a ring that blooms outward. The indeterminate prop is written to the DOM property, since HTML has no attribute for it. A CheckboxGroup export wraps options in a fieldset with a tri-state "select all" parent that is checked, mixed or empty as its children change.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/animated-checkbox.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/animated-checkbox.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/animated-checkbox.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { AnimatedCheckbox } from "@/components/ui/animated-checkbox";

export function AnimatedCheckboxExample() {
  return (
    <AnimatedCheckbox
      label="Email me product updates"
      description="At most one email a month."
      name="updates"
      defaultChecked
    />
  );
}

03Props

PropTypeDefaultDescription
checked / defaultCheckedboolean— / falseControlled state, or the initial state when uncontrolled.
indeterminatebooleanfalseMixed state; synced to the input's DOM indeterminate property and drawn as a dash.
onCheckedChange(checked: boolean) => void—Called with the next state. The native onChange also fires.
label / descriptionReactNode—Visible label (clicking it toggles) and secondary text linked via aria-describedby.
size"sm" | "md" | "lg""md"Box and text size.
accentstring"#FF7448"Fill colour of the checked box and the focus ring.
durationnumber280Check stroke draw duration in ms.
pressScalenumber0.85Box scale while pressed; 1 disables the press spring.
...propsInputHTMLAttributes<HTMLInputElement>—Native input attributes (name, value, required, disabled, aria-*) go to the real checkbox.
CheckboxGroup{ legend, options, value?, defaultValue?, onChange?, selectAll?, name?, size?, accent?, duration?, pressScale? }—Fieldset of checkboxes with a tri-state select-all parent (selectAll={false} hides it).

04Features

  • 01Native <input type="checkbox"> underneath: forms, required, labels and Space work unchanged
  • 02Check drawn with an animated stroke (pathLength / stroke-dashoffset)
  • 03Indeterminate state morphs the same path into a dash and is wired to the DOM property
  • 04Spring press on the box and a ring that blooms outward when it turns on
  • 05CheckboxGroup with a tri-state select-all parent and a live "n of m" count
  • 06Controlled or uncontrolled; three sizes and an accent colour
motion · springmotion · tweeninput · clickinput · pressinput · keyboard

05Accessibility

  • The real checkbox receives focus and clicks, so its role, checked and mixed states come from the browser, not ARIA imitation.
  • The label wraps the input; the description is linked with aria-describedby.
  • The select-all parent reports mixed via the indeterminate property and lists its children in aria-controls; the group is a <fieldset> with a <legend>.
  • Focus shows a 2px accent outline around the box (focus-visible only).
  • prefers-reduced-motion shows and hides the check instantly, with no press spring or bloom.