# Check List
URL: https://remocn.dev/docs/ui-blocks/check-list

A handwritten checklist that writes itself out, then ticks and strikes through every done item

- Install: `npx shadcn@latest add @remocn/check-list`
- Vibe: paper
- Natural length: 123f @ 30fps

## Installation

```bash
npx shadcn@latest add @remocn/check-list
```

## Usage

The list runs in two phases. First it **writes itself out**: each row draws its
box closed and then has its label written inside, with rows starting `itemGap`
frames apart and free to overlap. Only once the **whole** list has finished
writing does the second phase begin — each done item gets its tick, then a
stroke dragged through its label, one item after another `closeGap` frames
apart.

That ordering is the point: you read the full list before anything starts being
crossed off.

```tsx
// src/Root.tsx
import { AbsoluteFill, Composition, Sequence } from "remotion";
import { CheckList, checkListDuration } from "@/components/remocn/check-list";

const items = [
  "Render on your own machine",
  "No watermark, ever",
  "Every component MIT",
  { text: "Ships as source", checked: false },
];

const LIST_FROM = 12;
const LIST_DURATION = checkListDuration(items);

const CheckListScene = () => (
  <AbsoluteFill
    style={{
      background: "#f1eee7",
      alignItems: "center",
      justifyContent: "center",
    }}
  >
    <Sequence from={LIST_FROM} durationInFrames={LIST_DURATION}>
      <CheckList items={items} width={820} />
    </Sequence>
  </AbsoluteFill>
);

export const RemotionRoot = () => (
  <Composition
    id="CheckList"
    component={CheckListScene}
    durationInFrames={LIST_FROM + LIST_DURATION}
    fps={30}
    width={1280}
    height={720}
  />
);
```

### Items that never get ticked

A bare string is shorthand for an item that eventually gets ticked. Pass an
object with `checked: false` and the row keeps its box and its clean label
forever — no tick, no stroke through it. That is the "not yet" line in a
roadmap or a comparison, and it is skipped when the close phase counts out its
turns, so it never leaves a gap in the rhythm.

```tsx
<CheckList
  items={["Shipped today", { text: "Landing next", checked: false }]}
  width={620}
/>
```

### Sizing the scene

`checkListDuration` returns the frame the last stroke ends on. Pass the same
timing options you pass the component, and `delay` if you set it there — the
returned frame is absolute and includes it.

```tsx
const total = checkListDuration(items, { delay: 12, itemGap: 24, step: 4 });
```

`checkListEnterEnd` returns the frame the writing phase finishes on — the
moment the first tick lands. Use it to fire something else off the same beat,
such as a heading or a stamp arriving once the list is fully written.

```tsx
const written = checkListEnterEnd(items);
```

Both are maxima over the whole list, not the last row's: rows overlap, so an
early long label can outlast a later short one, and a list whose longest item
is unchecked still has that item decide when the writing phase ends. If nothing
is checked there is no close phase, and `checkListDuration` equals
`checkListEnterEnd`.

### Why width is required

Labels are written by `handwrite`, which centres itself absolutely. Inside an
auto-sized cell that collapses to zero width and the text silently vanishes, so
the list takes its width explicitly and derives every label cell, box offset,
and row from it. The component sets no background and no jitter of its own —
wrap it in `paper-wobble` to put it on the desk.

## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `items` (required) | `(string \| { text: string; checked?: boolean })[]` | — | Rows to draw; a bare string is an item that gets ticked |
| `width` (required) | `number` | — | List width; the label cell, box offset, and rows derive from it |
| `fontSize` | `number` | `40` | Label size; the box and row height scale from it |
| `color` | `string` | `"#26242c"` | Ink colour of the labels |
| `boxColor` | `string` | `"#26242c"` | Ink colour of the boxes |
| `tickColor` | `string` | `"#6f7f35"` | Ink colour of the ticks and the strokes through done labels |
| `delay` | `number` | `0` | Local frame the first box starts drawing |
| `itemGap` | `number` | `step * 6` | Frames between item starts while the list writes itself; rows may overlap |
| `closeGap` | `number` | `step * 3` | Frames between one item being ticked and the next, after the list is written |
| `rowGap` | `number` | `fontSize * 0.55` | Pixels between rows |
| `strokeWidth` | `number` | `3` | Pen width of the box; the tick is one unit heavier |
| `perStep` | `number` | `1.6` | Letters written per pose, forwarded to the labels |
| `weight` | `400 \| 500 \| 600 \| 700` | `600` | Label font weight |
| `seed` | `string` | `"checklist"` | Seed for the per-box corner wobble |
| `step` | `number` | `3` | Frames per stop-motion pose |

## Use when
- A list of features, steps, or done-items should be read in full and then crossed off in a hand-made scene.
- A feature-announcement beat needs the ticking and crossing-off gesture as its payoff.
- One line in the list has not landed yet — mark it `checked: false` and it stays clean.

## Don't use when
- Labels need to wrap across lines, or the list needs numbering or nesting. One label, one line.
- The list should sit at an angle or wobble — this component has no jitter and no positioning. Wrap it in `paper-wobble`.