Skip to content

Animated Checkbox

A checkbox whose checkmark draws itself in as a real SVG path animation.

Freeminimalplayful
Category
Components
Added
2026-08-22
Deps
1
Updated
2026-09-01
Preview

Props

Default checked
Requires framer-motion
"use client";

import { useState } from "react";
import { motion } from "framer-motion";

export function AnimatedCheckbox({
  label = "Send me the changelog",
  defaultChecked = false,
}: {
  label?: string;
  defaultChecked?: boolean;
}) {
  const [checked, setChecked] = useState(defaultChecked);

  return (
    <label className="flex cursor-pointer items-center gap-3 select-none">
      <span
        role="checkbox"
        aria-checked={checked}
        tabIndex={0}
        onClick={() => setChecked((v) => !v)}
        onKeyDown={(e) => {
          if (e.key === " " || e.key === "Enter") {
            e.preventDefault();
            setChecked((v) => !v);
          }
        }}
        className="relative flex h-6 w-6 shrink-0 items-center justify-center rounded-md border-2 border-neutral-300 transition-colors"
        style={{ borderColor: checked ? "#ef4444" : undefined, backgroundColor: checked ? "#ef4444" : "transparent" }}
      >
        <motion.svg viewBox="0 0 24 24" className="h-4 w-4" fill="none">
          <motion.path
            d="M5 13l4 4L19 7"
            stroke="white"
            strokeWidth={3}
            strokeLinecap="round"
            strokeLinejoin="round"
            initial={false}
            animate={{ pathLength: checked ? 1 : 0, opacity: checked ? 1 : 0 }}
            transition={{ duration: 0.3, ease: [0.65, 0, 0.35, 1] }}
          />
        </motion.svg>
      </span>
      <span className="text-sm font-medium">{label}</span>
    </label>
  );
}
Notes

About this component

A native checkbox gives no room to signal that a choice actually registered beyond a color flip, and most custom checkbox implementations that try to fix this just swap in a static check icon, which reads the same way. This one animates the SVG path's pathLength from 0 to 1 so the tick draws itself in stroke by stroke, paired with an opacity fade so there is no flash of a fully-drawn-but-invisible path. Reach for it over a plain CSS checkbox when that draw-in motion is worth the framer-motion import; in a dense settings list with dozens of checkboxes, the visual noise of that many ticks drawing in at once argues for a plain native element instead. The part worth noticing: this is not a hidden native input with a styled sibling, it is a role="checkbox" span with manual Space/Enter handling, because a native input cannot host the SVG check mark as its own animatable child the way pathLength needs. One real limitation: it does not wire into a native form's checked/value submission, so form libraries need to be pointed at it explicitly.

Curator’s note

pathLength is the whole trick — animating opacity or swapping an icon looks fine in a screenshot but reads as flat next to an actual stroke draw-in. It's one prop on the path, easy to skip, obvious once you see it.

Related

Pairs well with

Matched on shared tags and category — the entries most likely to be used alongside this one.

A 5-star rating control with hover preview and a springy fill pop.

ratingstarsform

A track+thumb switch whose icon morphs between a sun and a crescent moon.

themetogglesun
NewPro

A segmented control where only the selected option expands to its label.

segmentedtogglemorph