Menu

Change language

GitHub repo

Onboarding Checklist

blockauth

Progressive 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

$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/onboarding-checklist.json
Manifest:https://uipkge.dev/r/svelte/onboarding-checklist.jsonInstalls to:src/lib/components/blocks/onboarding-checklist/

Variants

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