Onboarding Checklist
blockauthProgressive activation checklist card: header with title, an N of M complete counter and a linear Progress bar, tappable task rows with icon tiles, one-line descriptions and chevrons (completed tasks flip to a filled check tile with muted copy), a footer with a Skip for now ghost link and an X dismiss that fades the card out, and a success panel with a glowing check circle at 5 of 5. State is local — wire tasks to your own activation API.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/onboarding-checklist.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/onboarding-checklist.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/onboarding-checklist.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/onboarding-checklist.jsonManifest:
https://uipkge.dev/r/svelte/onboarding-checklist.jsonInstalls to:src/lib/components/blocks/onboarding-checklist/Variants
Loading interactive previews…
Loading interactive previews…
npm dependencies
Files installed (1)
src/lib/components/blocks/onboarding-checklist/OnboardingChecklist.svelte5.9 kB
<script lang="ts" module> import type { Component } from 'svelte' export interface OnboardingTask { id: string title: string description: string icon: Component done: boolean } export interface OnboardingChecklistProps { /** Seed every task as done so the success panel can be previewed in isolation. */ initialComplete?: boolean class?: string } </script> <script lang="ts"> import { Bell, BookOpen, Building2, Check, ChevronRight, Database, Users, X } from '@lucide/svelte' import { cn } from '$lib/utils' import { Button } from '$lib/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '$lib/components/ui/card' import { Progress } from '$lib/components/ui/progress' let { initialComplete = false, class: className }: OnboardingChecklistProps = $props() const baseTasks: Omit<OnboardingTask, 'done'>[] = [ { id: 'workspace', title: 'Create workspace', description: 'Name your team space and pick a URL.', icon: Building2, }, { id: 'teammates', title: 'Invite teammates', description: 'Bring in the people you work with.', icon: Users, }, { id: 'data', title: 'Connect your data', description: 'Sync sources so insights flow in automatically.', icon: Database, }, { id: 'alerts', title: 'Set up alerts', description: 'Get notified when metrics move.', icon: Bell, }, { id: 'api', title: 'Explore API docs', description: 'Learn the endpoints and ship your first request.', icon: BookOpen, }, ] let tasks = $state<OnboardingTask[]>( baseTasks.map((task, index) => ({ ...task, done: initialComplete || index < 3, })), ) let open = $state(true) const completed = $derived(tasks.filter((task) => task.done).length) const total = $derived(tasks.length) const percent = $derived(Math.round((completed / total) * 100)) const allDone = $derived(completed === total) function toggle(id: string) { const task = tasks.find((t) => t.id === id) if (task) task.done = !task.done } </script> <div data-slot="onboarding-checklist" class={cn( 'mx-auto w-full max-w-md transition-[opacity,translate] duration-200 ease-out motion-reduce:transition-none', !open && 'pointer-events-none translate-y-1 opacity-0', className, )} > <Card> <CardHeader> <CardTitle>Get started</CardTitle> <CardDescription>Finish these steps to unlock everything your workspace can do.</CardDescription> </CardHeader> <CardContent class="space-y-4"> <div class="space-y-2"> <div class="flex items-center justify-between"> <p class="text-muted-foreground text-xs"> <span class="text-foreground font-medium">{completed} of {total}</span> complete </p> <p class="text-muted-foreground text-xs tabular-nums">{percent}%</p> </div> <Progress value={percent} /> </div> {#if allDone} <div class="space-y-4 py-6 text-center"> <div class="border-success/30 bg-success/10 text-success mx-auto flex size-16 items-center justify-center rounded-full border shadow-xs" > <Check class="size-8" aria-hidden="true" /> </div> <div> <p class="text-lg font-semibold">You're all set</p> <p class="text-muted-foreground mt-1 text-sm">Every step is complete — your workspace is ready to go.</p> </div> </div> {:else} <div class="-mx-2 divide-y"> {#each tasks as task (task.id)} {@const Icon = task.icon} <button type="button" role="checkbox" aria-checked={task.done} class="group hover:bg-muted/50 focus-visible:ring-ring flex w-full items-center gap-3 rounded-lg p-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none" onclick={() => toggle(task.id)} > {#if task.done} <span class="bg-primary text-primary-foreground flex size-9 shrink-0 items-center justify-center rounded-md shadow-xs" > <Check class="size-4" aria-hidden="true" /> </span> {:else} <span class="border-border bg-muted text-muted-foreground group-hover:bg-background group-hover:text-foreground flex size-9 shrink-0 items-center justify-center rounded-md border transition-colors" > <Icon class="size-4" aria-hidden="true" /> </span> {/if} <span class="min-w-0 flex-1"> <span class={cn('block truncate text-sm font-medium', task.done && 'text-muted-foreground')}> {task.title} </span> <span class="text-muted-foreground mt-0.5 block truncate text-xs">{task.description}</span> </span> <ChevronRight class="text-muted-foreground/50 size-4 shrink-0 transition-transform duration-200 group-hover:translate-x-0.5" aria-hidden="true" /> </button> {/each} </div> {/if} </CardContent> <CardFooter class="justify-between"> {#if !allDone} <Button variant="ghost" size="sm" onclick={() => (open = false)}>Skip for now</Button> {:else} <span aria-hidden="true"></span> {/if} <button type="button" aria-label="Dismiss checklist" class="hover:bg-foreground/10 focus-visible:ring-ring rounded-md p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none" onclick={() => (open = false)} > <X class="text-muted-foreground size-4" aria-hidden="true" /> </button> </CardFooter> </Card> </div>
Raw manifest:https://uipkge.dev/r/svelte/onboarding-checklist.json