# Onboarding checklist

> A setup checklist card with a progress bar, completed and pending tasks, and start links for remaining steps.

A shadcn/ui block from the [@quill](https://www.quilldesignsystem.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/quill/onboarding).

- Page, with a live preview: https://shadcn.exchange/r/quill/onboarding
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/quill/onboarding-light-115f526a685e8497.png
- Registry item: https://www.quilldesignsystem.com/r/onboarding.json
- Categories: productivity
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.quilldesignsystem.com/r/onboarding.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `card`, `button`, `https://www.quilldesignsystem.com/r/icon.json`

## Notes from the registry

A setup checklist card with a progress bar, completed and pending tasks, and start links for remaining steps. Do: Pair the visual progress bar with a text fraction ("2 of 4 complete") so the state isn't conveyed by bar width alone. Don't: Show only a progress bar with no numeric readout of how much is actually done. Use wizard when the steps must happen in a strict sequence in one sitting, not a checklist a user dips in and out of over time. Use empty-state when there's a single action to take, not a multi-step checklist to track.

## Files

### `registry/blocks/onboarding.tsx`

```tsx
import {
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
} from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Icon } from '@/components/ui/icon'

const tasks = [
  { label: 'Create your account', done: true },
  { label: 'Name your workspace', done: true },
  { label: 'Invite your team', done: false },
  { label: 'Connect a repository', done: false },
]

/** A setup checklist card with a progress bar, completed and pending tasks, and start links for remaining steps. */
export function Onboarding() {
  const done = tasks.filter((t) => t.done).length
  return (
    <Card className="w-[400px]">
      <CardHeader>
        <CardTitle>Get set up</CardTitle>
        <CardDescription>
          {done} of {tasks.length} complete — you’re almost there.
        </CardDescription>
        <div className="mt-1 h-2 overflow-hidden rounded-full bg-[color-mix(in_oklab,var(--input)_10%,var(--muted))]">
          <div
            className="h-full rounded-full bg-primary"
            style={{ width: `${(done / tasks.length) * 100}%` }}
          />
        </div>
      </CardHeader>
      <CardContent className="flex flex-col gap-2">
        {tasks.map((t) => (
          <div key={t.label} className="flex items-center gap-3 rounded-lg px-1 py-1.5">
            <span
              className={`flex size-5 items-center justify-center rounded-full ${
                t.done ? 'bg-primary text-primary-foreground' : 'border border-input'
              }`}
            >
              {t.done && <Icon name="check" size={13} />}
            </span>
            <span className={`flex-1 text-sm ${t.done ? 'text-muted-foreground line-through' : 'text-foreground'}`}>
              {t.label}
            </span>
            {!t.done && (
              <Button variant="link" size="sm">
                Start
              </Button>
            )}
          </div>
        ))}
      </CardContent>
    </Card>
  )
}
```
