# First-run Checklist

> An animated first-run checklist for React and shadcn, docked in your app: items tick in any order as people do them, and it folds into a ring.

The checklist that lives in the product after setup. Each item opens the part of the app that does it, and the item ticks when the app says it happened, in any order: the tick draws, the label strikes through, the ring fills and the count rolls. It folds into a small ring button, keeps progress across reloads, and ends on "You’re set up" with what to do next.

- Tier: Eased Pro
- Interaction: Press
- Page: https://eased.dev/components/first-run-checklist
- Installs: motion

## Install

First-run Checklist ships with Eased Pro (https://eased.dev/#pricing). Members add the `@eased-pro` registry with their token (https://eased.dev/account), then run:

```bash
npx shadcn add @eased-pro/first-run-checklist
```

A checklist docked in the corner of your product for the first minutes after setup: each item opens the part of the app that does it, and ticks when the app says it happened, in any order. Built on Onboarding Checklist and the Onboarding core step engine.

## When to use

- After sign-up and setup, inside the product: a handful of first actions (a project, an invite, an integration) people do in the real UI, not in a wizard.
- When the actions happen in different places and in any order, so each must tick where it really happens.
- When progress should survive a reload and the checklist should get out of the way, folded into a ring, until it is done.

## When not to use

- Questions to answer before the product is usable: a step-by-step flow on [Onboarding Shell](/components/onboarding-shell), such as [Workspace Setup](/components/workspace-setup).
- A list on a settings or home page that ticks when its own button is pressed: [Onboarding Checklist](/components/onboarding-checklist) on its own.
- Pointing at one control at a time: a tour with a spotlight.

## Usage

```tsx example.tsx
import { FirstRunChecklist, useFirstRunChecklist } from "@/components/eased/first-run-checklist/first-run-checklist";

const ITEMS = [
  { id: "project", title: "Create your first project", description: "Projects hold your issues and docs.", action: "New project", minutes: 1 },
  { id: "invite", title: "Invite your team", action: "Invite people", minutes: 1 },
  { id: "github", title: "Connect GitHub", action: "Connect GitHub", minutes: 2 },
] as const;

export function GettingStarted() {
  const router = useRouter();
  const checklist = useFirstRunChecklist({
    items: ITEMS,
    persist: { key: `lumen:first-run:${user.id}` },
    onComplete: () => api.markSetUp(),
  });

  // Wherever the action really happens: checklist.complete("project")
  useEvent("project.created", () => checklist.complete("project"));

  return (
    <FirstRunChecklist
      checklist={checklist}
      onOpen={(id) => router.push(`/start/${id}`)}
      next={[{ id: "cycles", label: "Plan your first cycle", onSelect: () => router.push("/cycles") }]}
      className="fixed bottom-4 end-4"
    />
  );
}
```

Share the controller through context or your store, so the page where a project is created can call `complete("project")`.

## Examples

### Start folded

Show the ring button first, and let people open the list when they want it.

```tsx
<FirstRunChecklist checklist={checklist} defaultCollapsed className="fixed bottom-4 end-4" />
```

### Keep the done state on your server

`persist` takes any `StepsStorage`, so progress can follow someone across devices.

```tsx
useFirstRunChecklist({
  items: ITEMS,
  persist: {
    key: user.id,
    storage: { get: (k) => api.getProgress(k), set: (k, v) => api.saveProgress(k, v), remove: (k) => api.clearProgress(k) },
  },
});
```

## API reference

### useFirstRunChecklist

| Option | Type | Default | Description |
|---|---|---|---|
| `items` | `FirstRunItem[]` | | `{ id, title, description?, action?, minutes?, when? }`. `action` is the open item's button; `when(data)` leaves an item out. |
| `persist` | `StepsPersist` | off | Save progress across reloads. The finished state is kept too: `clearOnComplete` defaults to `false` here. |
| `onComplete` | `() => void \| Promise` | | Runs once every item is done. Record there that this person is set up. |

Returns `{ flow, done, complete(id), undo(id) }`. `flow` is the `useSteps` flow underneath: `complete` is its `mark(id, "complete")`, and it calls `finish()` when the last item lands.

### FirstRunChecklist

| Prop | Type | Default | Description |
|---|---|---|---|
| `checklist` | `FirstRunController` | | From `useFirstRunChecklist`. |
| `onOpen` | `(id) => void` | | An item's action was chosen: open that part of your app. The item ticks when you call `complete`, not here. |
| `next` | `FirstRunNext[]` | `[]` | `{ id, label, description?, onSelect? }`, shown under "You’re set up". |
| `title` | `string` | `"Get started"` | |
| `doneTitle` | `string` | `"You’re set up"` | |
| `collapsed` | `boolean` | | Controlled: folded into the ring button. |
| `defaultCollapsed` | `boolean` | `false` | |
| `onCollapsedChange` | `(collapsed: boolean) => void` | | |
| `locale` | `string` | | For the count. |
| `className` | `string` | `""` | Position it, e.g. `fixed bottom-4 end-4`. |

## Keyboard

| Key | Action |
|---|---|
| `Tab` | Moves through the hide button, each item's tick and label, and the open item's action. |
| `Enter` / `Space` | On a label, opens that item; on its action, opens the part of the app; on a tick, marks it done or not. |

## Accessibility

- The panel is a labelled region; each item's tick is a checkbox named by its label, so it can be marked done by hand.
- The line under the title ("3 steps left, about 4 min") is a polite live region, so a tick made elsewhere in the app is announced.
- The folded button says how many are done ("Get started, 2 of 5 done") and is `aria-expanded="false"`; folding and opening move focus between it and the hide button.
- An item's action leaves focus alone, so your `onOpen` can move it into the part of the app it opened; the item stays open until you call `complete`.

## Motion

The signature is the checklist reacting to the app: when an action happens elsewhere, the matching item's tick draws, its label strikes through left to right, the ring fills on `smooth` and the count rolls, with the next item unfolding. The panel and the ring button swap from the bottom corner (rise 6px from 0.97 on the enter curve; leave 3px to 0.98 in 100ms), and the folded ring keeps filling. Once everything is done the list folds into its Done pill, the title swaps to "You’re set up" and what's next opens under it on a height spring. Reduced motion: states change at once and the swaps fade.

## Notes for AI

Use it inside the product after setup. Call `complete(id)` where each action really happens (the create handler, the invite response, the OAuth callback), never from `onOpen`. Give `persist` a key per person.

Don't rebuild the list, ring or ticks: they are Onboarding Checklist's. Don't put it in a dialog or a page; it is a floating dock, positioned with `className`.

## Related

- [Onboarding Checklist](/components/onboarding-checklist): the list, ring and ticks under this dock.
- [Workspace Setup](/components/workspace-setup): the flow before it.
- [Onboarding Shell](/components/onboarding-shell): the step engine and frame for wizard-style flows.
- [Onboarding set](/sets/onboarding): Workspace Setup, Team Invite and this checklist chained into one first run.
