Checkbox whose check mark draws itself as an SVG path with a squash-and-settle box pop, plus an optional animated strike-through across the label.
Preview
Customize
5 propsInstallation
Copies the component source and its CSS Module into your project.
npx motionfoundry add animated-checkboxWrites components/ui/animated-checkbox.tsx and components/styles/animated-checkbox.module.css.
Usage
Updates live as you change the controls — only props that differ from the defaults are included.
1import { AnimatedCheckbox } from "@motionfoundry/ui";2 3export function Example() {4 return (5 <AnimatedCheckbox />6 );7}Props
Accessibility
Renders a <button role="checkbox"> with aria-checked and the label as accessible name (fallback aria-label "Checkbox"); the box, check path and strike line are aria-hidden. Space/Enter toggle natively, and prefers-reduced-motion shows the final state instantly.
Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.
Source
1"use client";2 3import * as React from "react";4import { motion, useReducedMotion } from "motion/react";5import styles from "../styles/animated-checkbox.module.css";6 7export interface AnimatedCheckboxProps {8 /** Controlled checked state */9 checked?: boolean;10 /** Initial state when uncontrolled */11 defaultChecked?: boolean;12 /** Called with the next state when toggled */13 onCheckedChange?: (checked: boolean) => void;14 /** Visible label (also the accessible name) */15 label?: string;16 /** Draw a strike-through line across the label when checked */17 strikeThrough?: boolean;18 /** Box fill color when checked */19 accentColor?: string;20 /** Disable interaction */21 disabled?: boolean;22 /** Optional className */23 className?: string;24}25 26export function AnimatedCheckbox({27 checked,28 defaultChecked = false,29 onCheckedChange,30 label = "Ship the release notes",31 strikeThrough = true,32 accentColor = "#38bdf8",33 disabled = false,34 className = "",35}: AnimatedCheckboxProps) {36 const prefersReducedMotion = useReducedMotion();37 const [inner, setInner] = React.useState(defaultChecked);38 const isControlled = checked !== undefined;39 const on = isControlled ? checked : inner;40 41 const toggle = () => {42 if (disabled) return;43 const next = !on;44 if (!isControlled) setInner(next);45 onCheckedChange?.(next);46 };47 48 const ease = [0.65, 0, 0.35, 1] as const;49 50 return (51 <button52 type="button"53 role="checkbox"54 aria-checked={on}55 aria-label={label ? undefined : "Checkbox"}56 disabled={disabled}57 onClick={toggle}58 data-state={on ? "checked" : "unchecked"}59 style={{ "--mf-ac-accent": accentColor } as React.CSSProperties}60 className={`${styles.root} ${className}`.trim()}61 >62 <motion.span63 aria-hidden="true"64 className={styles.box}65 initial={false}66 animate={prefersReducedMotion ? { scale: 1 } : { scale: on ? [1, 0.82, 1.06, 1] : 1 }}67 transition={{ duration: prefersReducedMotion ? 0 : 0.35 }}68 >69 <svg viewBox="0 0 24 24" className={styles.icon}>70 <motion.path71 d="M5 12.5 L10 17.5 L19 7"72 fill="none"73 stroke="currentColor"74 strokeWidth="3"75 strokeLinecap="round"76 strokeLinejoin="round"77 initial={false}78 animate={{ pathLength: on ? 1 : 0, opacity: on ? 1 : 0 }}79 transition={{80 pathLength: { duration: prefersReducedMotion ? 0 : 0.3, ease, delay: on && !prefersReducedMotion ? 0.06 : 0 },81 opacity: { duration: prefersReducedMotion ? 0 : 0.05, delay: on ? 0 : 0.25 },82 }}83 />84 </svg>85 </motion.span>86 {label ? (87 <span className={styles.label}>88 {label}89 {strikeThrough ? (90 <motion.span91 aria-hidden="true"92 className={styles.strike}93 initial={false}94 animate={{ scaleX: on ? 1 : 0 }}95 transition={{ duration: prefersReducedMotion ? 0 : 0.35, ease, delay: on && !prefersReducedMotion ? 0.15 : 0 }}96 />97 ) : null}98 </span>99 ) : null}100 </button>101 );102}