Checklist
Rows that check themselves off with the existing stroke-draw check technique, accelerating pace as the list winds down, and mute once checked.
Checklist, defaults
Installation
npx shadcn add https://www.reelcn.dev/r/checklist.jsonFiles land in src/reelcn/ with the items it builds on.
Usage
<Center>
<Checklist items={[{ text: "Connect your repo" }, { text: "Add a webhook" }, { text: "Ship your first release" }]} />
</Center>Props
PropTypeDescription
delaynumberFrames to wait before entering.
durationnumberEnter duration in frames. Defaults to 0.6s. Some items reuse
duration for an unrelated local timing. See that item's own prop docs.exitnumber | booleanExit at the end of the parent `<Sequence>`.
false keeps it on screen, a number sets the exit length in frames.motionMotionPersonalityOverride the theme's motion personality.
posterbooleanRenders fully entered, with no exit: for a still frame or thumbnail. Defaults to
false.holdFramesnumberDelays the start of the exit window by this many frames, for a minimum on-screen hold. Defaults to
0.items*{ text: string; at?: number; }[]
at in seconds; omitted entries derive a content-based, accelerating schedule (`checklistSchedule`).checkColorstring
styleCSSProperties
classNamestring
Built from
Use
- A features/setup-steps list that completes itself in a product demo
- Any "here's everything done" beat that shouldn't feel mechanically linear
Avoid
- A single item. Use plain text with
svg-draw's check path directly
Guides
Components take their colors from a theme, their timing from motion props and their layout from the canvas.
Source
registry/items/checklist.tsx
/**
* @title Checklist
* @category product
* @description Rows that check themselves off with the existing stroke-draw check technique, accelerating pace as the list winds down, and mute once checked.
* @duration data-driven
* @use A features/setup-steps list that completes itself in a product demo
* @use Any "here's everything done" beat that shouldn't feel mechanically linear
* @avoid A single item. Use plain text with `svg-draw`'s check path directly
* @tags checklist, checkmark, list, tasks, stagger
* @example
* <Center>
* <Checklist items={[{ text: "Connect your repo" }, { text: "Add a webhook" }, { text: "Ship your first release" }]} />
* </Center>
*/
import type React from "react";
import {
checklistSchedule,
clamp01,
type MotionProps,
StrokeOverlay,
tween,
useMotion,
useTheme,
useViewport,
} from "./core";
export type ChecklistProps = MotionProps & {
/** `at` in seconds; omitted entries derive a content-based, accelerating schedule (`checklistSchedule`). */
items: { text: string; at?: number }[];
checkColor?: string;
style?: React.CSSProperties;
className?: string;
};
const ROW_HEIGHT = 52;
const CHECK_SIZE = 22;
const CHECK = "M4 12l5 5L20 6";
export function Checklist({ items, checkColor, style, className, ...motion }: ChecklistProps) {
const theme = useTheme();
const { u } = useViewport();
const m = useMotion(motion);
const schedule = checklistSchedule(items);
const check = checkColor ?? theme.colors.success;
return (
<div
className={className}
style={{ display: "flex", flexDirection: "column", gap: u(4), opacity: m.presence, ...style }}
>
{items.map((item, i) => {
const arrivalFrame = m.delay + (item.at !== undefined ? Math.round(item.at * m.fps) : schedule[i]);
const drawn = clamp01(
tween(m.frame, m.fps, { from: arrivalFrame, duration: Math.round(m.fps * 0.35), motion: "smooth" }),
);
const settled = clamp01(
tween(m.frame, m.fps, {
from: arrivalFrame + Math.round(m.fps * 0.35),
duration: Math.round(m.fps * 0.4),
motion: "smooth",
}),
);
return (
<div key={item.text} style={{ display: "flex", alignItems: "center", gap: u(14), height: u(ROW_HEIGHT) }}>
<svg aria-hidden="true" width={u(CHECK_SIZE)} height={u(CHECK_SIZE)} viewBox="0 0 24 24">
<circle cx={12} cy={12} r={10.5} fill="none" stroke={check} strokeWidth={1.6} opacity={drawn} />
<StrokeOverlay
d={CHECK}
kind="vector"
seed={`checklist-${i}`}
color={check}
strokeWidth={2.4}
drawn={drawn}
extraProps={{ strokeLinecap: "round", strokeLinejoin: "round" }}
/>
</svg>
<span
style={{
fontFamily: theme.fonts.body,
fontSize: u(22),
color: `color-mix(in srgb, ${theme.colors.muted} ${Math.round(settled * 100)}%, ${theme.colors.foreground})`,
}}
>
{item.text}
</span>
</div>
);
})}
</div>
);
}