UIPackage
Menu

Framework

Change language

Boilerplate 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.

Also available for React ->

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/onboarding-checklist.json
Named registry:npx shadcn-vue@latest add @uipkge/onboarding-checklistInstalls to:app/components/blocks/

Variants

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
initialComplete

Seed every task as done so the success panel can be previewed in isolation.

booleanfalseoptional
classHTMLAttributes['class']optional

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

Task
interface Task {
  id: string
  title: string
  description: string
  icon: Component
  done: boolean
}

npm dependencies

Files installed (1)

  • app/components/blocks/OnboardingChecklist.vue5.8 kB
    <script setup lang="ts">
    import { computed, ref } from 'vue'
    import type { Component } from 'vue'
    import type { HTMLAttributes } from 'vue'
    import { Bell, BookOpen, Building2, Check, ChevronRight, Database, Users, X } from 'lucide-vue-next'
    import { cn } from '@/lib/utils'
    import { Button } from '@/components/ui/button'
    import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
    import { Progress } from '@/components/ui/progress'
    
    interface Task {
      id: string
      title: string
      description: string
      icon: Component
      done: boolean
    }
    
    const props = withDefaults(
      defineProps<{
        /** Seed every task as done so the success panel can be previewed in isolation. */
        initialComplete?: boolean
        class?: HTMLAttributes['class']
      }>(),
      {
        initialComplete: false,
      },
    )
    
    const baseTasks: Omit<Task, '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,
      },
    ]
    
    const tasks = ref<Task[]>(
      baseTasks.map((task, index) => ({
        ...task,
        done: props.initialComplete || index < 3,
      })),
    )
    
    const open = ref(true)
    
    const completed = computed(() => tasks.value.filter((task) => task.done).length)
    const total = computed(() => tasks.value.length)
    const percent = computed(() => Math.round((completed.value / total.value) * 100))
    const allDone = computed(() => completed.value === total.value)
    
    function toggle(id: string) {
      const task = tasks.value.find((t) => t.id === id)
      if (task) task.done = !task.done
    }
    
    function dismiss() {
      open.value = false
    }
    </script>
    
    <template>
      <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',
            props.class,
          )
        "
      >
        <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 :model-value="percent" />
            </div>
    
            <div v-if="allDone" 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>
    
            <div v-else class="-mx-2 divide-y">
              <button
                v-for="task in tasks"
                :key="task.id"
                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"
                @click="toggle(task.id)"
              >
                <span
                  v-if="task.done"
                  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>
                <span
                  v-else
                  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"
                >
                  <component :is="task.icon" class="size-4" aria-hidden="true" />
                </span>
                <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>
            </div>
          </CardContent>
          <CardFooter class="justify-between">
            <Button v-if="!allDone" variant="ghost" size="sm" @click="dismiss">Skip for now</Button>
            <span v-else aria-hidden="true"></span>
            <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"
              @click="dismiss"
            >
              <X class="text-muted-foreground size-4" aria-hidden="true" />
            </button>
          </CardFooter>
        </Card>
      </div>
    </template>
    

Raw manifest:https://uipkge.dev/r/vue/onboarding-checklist.json