# Checkbox Todo Strikethrough — React Checkbox Component

> A to-do list where ticking an item draws a line through its text from left to right, then the finished item sinks to the bottom with a layout spring.

- Category: [Checkboxes & Radios](https://pixelpapa.com/components/checkboxes.md)
- Tags: checkbox, todo, strikethrough, list, layout
- Dependencies: motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/checkboxes/checkbox-todo-strikethrough

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/checkbox-todo-strikethrough.json"
```

The file is written to `components/pixelpapa/checkbox-todo-strikethrough.tsx`. To install manually, run `npm install motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { motion } from "motion/react";
import { useState, type ReactNode } from "react";
import { cn } from "@/lib/utils";

type TodoItemProps = {
  children: ReactNode;
  checked: boolean;
  onCheckedChange: (checked: boolean) => void;
  className?: string;
};

export function TodoItem({ children, checked, onCheckedChange, className }: TodoItemProps) {
  return (
    <label className={cn("group flex cursor-pointer items-start gap-3 rounded-lg px-2 py-2 hover:bg-muted/60", className)}>
      <motion.span whileTap={{ scale: 0.82 }} className="relative mt-px size-5 shrink-0">
        <input
          type="checkbox"
          checked={checked}
          onChange={(e) => onCheckedChange(e.target.checked)}
          className="peer absolute inset-0 z-10 size-full cursor-pointer opacity-0"
        />
        <span
          aria-hidden
          className="absolute inset-0 rounded-full border-[1.5px] border-input transition-colors group-hover:border-primary/60 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-card"
        />
        <motion.span
          aria-hidden
          initial={false}
          animate={{ scale: checked ? 1 : 0 }}
          transition={{ type: "spring", stiffness: 540, damping: 24 }}
          className="absolute inset-0 rounded-full bg-primary"
        />
        <svg aria-hidden viewBox="0 0 20 20" fill="none" className="absolute inset-0 text-primary-foreground">
          <motion.path
            d="M6 10.4l2.8 2.8 5.4-6"
            stroke="currentColor"
            strokeWidth="2.2"
            strokeLinecap="round"
            strokeLinejoin="round"
            initial={false}
            animate={{ pathLength: checked ? 1 : 0, opacity: checked ? 1 : 0 }}
            transition={{ pathLength: { duration: 0.22, delay: checked ? 0.08 : 0 }, opacity: { duration: 0.01, delay: checked ? 0.08 : 0 } }}
          />
        </svg>
      </motion.span>
      {/* The line is a background image on inline text, so it follows wrapped lines. */}
      <span
        className={cn(
          "bg-[linear-gradient(currentColor,currentColor)] bg-[position:0_56%] bg-no-repeat text-sm leading-5 transition-[background-size,color] duration-500 ease-out select-none motion-reduce:duration-0",
          checked ? "bg-[length:100%_1.5px] text-muted-foreground" : "bg-[length:0%_1.5px] text-foreground",
        )}
      >
        {children}
      </span>
    </label>
  );
}

type Todo = { id: string; text: string; done: boolean };

const initial: Todo[] = [
  { id: "copy", text: "Rewrite the pricing page headline", done: true },
  { id: "og", text: "Export social preview images at 1200 × 630", done: false },
  { id: "dns", text: "Point the apex domain at the new load balancer", done: false },
  { id: "qa", text: "Walk through checkout on a slow 3G connection", done: false },
  { id: "post", text: "Schedule the launch post for Tuesday 9:00", done: false },
];

export default function CheckboxTodoStrikethroughDemo() {
  const [todos, setTodos] = useState(initial);
  const done = todos.filter((t) => t.done).length;
  // Stable sort: open items keep their order, finished ones gather underneath.
  const ordered = [...todos].sort((a, b) => Number(a.done) - Number(b.done));

  return (
    <div className="w-full max-w-sm rounded-2xl border border-border bg-card p-4">
      <div className="flex items-baseline justify-between px-2">
        <p className="text-sm font-semibold text-card-foreground">Launch checklist</p>
        <p className="text-xs text-muted-foreground tabular-nums">
          {done} of {todos.length} done
        </p>
      </div>
      <div className="mx-2 mt-3 h-1 overflow-hidden rounded-full bg-muted">
        <motion.div
          className="h-full rounded-full bg-primary"
          initial={false}
          animate={{ width: `${(done / todos.length) * 100}%` }}
          transition={{ type: "spring", stiffness: 200, damping: 26 }}
        />
      </div>
      <ul className="mt-3">
        {ordered.map((todo) => (
          <motion.li key={todo.id} layout transition={{ type: "spring", stiffness: 380, damping: 34, delay: 0.25 }}>
            <TodoItem
              checked={todo.done}
              onCheckedChange={(v) => setTodos((prev) => prev.map((t) => (t.id === todo.id ? { ...t, done: v } : t)))}
            >
              {todo.text}
            </TodoItem>
          </motion.li>
        ))}
      </ul>
    </div>
  );
}
```

## AI prompt

```text
Add the "Checkbox Todo Strikethrough" component from PixelPapa UI to this project.

What it is: A to-do list where ticking an item draws a line through its text from left to right, then the finished item sinks to the bottom with a layout spring.
Category: Checkboxes & Radios. Depends on: motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/checkbox-todo-strikethrough.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/checkbox-todo-strikethrough.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/checkbox-todo-strikethrough" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More checkboxes & radios

- [Checkbox Card Select](https://pixelpapa.com/components/checkboxes/checkbox-card-select.md) (Pro): Multi-select cards: a chosen card tints, its icon tile inverts and a check bubble springs into the corner while the running total rolls.
- [Checkbox Draw Check](https://pixelpapa.com/components/checkboxes/checkbox-draw-check.md): A checkbox whose fill springs out from the centre while the tick is drawn as a single pen stroke, then erased in reverse when cleared.
- [Checkbox Indeterminate Tree](https://pixelpapa.com/components/checkboxes/checkbox-indeterminate-tree.md) (Pro): A nested checkbox tree where parents reflect their children: the mark morphs between a dash and a check, and branches fold open with animated height.
- [Radio Card Plans](https://pixelpapa.com/components/checkboxes/radio-card-plans.md): Plan cards backed by native radios: one shared selection ring glides from card to card with a spring while the radio dot pops in.
- [Radio Segmented Dots](https://pixelpapa.com/components/checkboxes/radio-segmented-dots.md) (Pro): A minimal radio group of dots on a hairline: a ringed marker bounces between them on a spring and the chosen option's name rolls in beside the label.
