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@latest add https://uipkge.dev/r/lit/onboarding-checklist.json$npx shadcn@latest add https://uipkge.dev/r/lit/onboarding-checklist.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/onboarding-checklist.json$bunx shadcn@latest add https://uipkge.dev/r/lit/onboarding-checklist.jsonManifest:
https://uipkge.dev/r/lit/onboarding-checklist.jsonInstalls to:src/uip/blocks/onboarding-checklist/Variants
Loading interactive previews…
Loading interactive previews…
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
initial-complete | boolean | false | optional |
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
ChecklistTaskinterface ChecklistTask {
id: string
heading: string
description: string
iconName: string
done: boolean
}HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-onboarding-checklist': UipOnboardingChecklist
}
}Files installed (1)
src/uip/blocks/onboarding-checklist/onboarding-checklist.ts8.3 kB
import { LitElement, css, html } from 'lit' import { Bell, BookOpen, Building2, Check, ChevronRight, Database, Users, X } from 'lucide' import { cn } from '../../lib/utils' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' import { icon } from '../../lib/icon' // Side-effect imports: register the composed elements for standalone use // (consumers who install only this block still get working primitives). import '../../components/button/button' import '../../components/card/card' import '../../components/progress/progress' interface ChecklistTask { id: string heading: string description: string iconName: string done: boolean } /** * <uip-onboarding-checklist> — the registry OnboardingChecklist block as a web component. * * Class strings are React's OnboardingChecklist verbatim. React state becomes * @state fields. `initial-complete` seeds every task as done (React's * `initialComplete`). React's `className` maps to classes on the host (per * CONVENTIONS: layout + inherited styles go on the host). Task icons are * static per-row markup (lucide via icon()), not a component map. */ export class UipOnboardingChecklist extends LitElement { static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true } // :host display can't be a utility class (the host has no template of its own). static styles = [tailwind, css`:host { display: block; }`] static properties = { initialComplete: { type: Boolean, attribute: 'initial-complete' }, tasks: { state: true }, open: { state: true }, } initialComplete = false private tasks: ChecklistTask[] = [] private open = true constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'onboarding-checklist') if (this.tasks.length === 0) { const seeds = [ { id: 'workspace', heading: 'Create workspace', description: 'Name your team space and pick a URL.' }, { id: 'teammates', heading: 'Invite teammates', description: 'Bring in the people you work with.' }, { id: 'data', heading: 'Connect your data', description: 'Sync sources so insights flow in automatically.' }, { id: 'alerts', heading: 'Set up alerts', description: 'Get notified when metrics move.' }, { id: 'api', heading: 'Explore API docs', description: 'Learn the endpoints and ship your first request.' }, ] this.tasks = seeds.map((t, i) => ({ ...t, iconName: t.id, done: this.initialComplete || i < 3 })) } } private toggle(id: string) { this.tasks = this.tasks.map((t) => (t.id === id ? { ...t, done: !t.done } : t)) } private taskIcon(id: string, cls: string) { switch (id) { case 'workspace': return icon(Building2, 'building-2', cls) case 'teammates': return icon(Users, 'users', cls) case 'data': return icon(Database, 'database', cls) case 'alerts': return icon(Bell, 'bell', cls) default: return icon(BookOpen, 'book-open', cls) } } render() { const completed = this.tasks.filter((t) => t.done).length const total = this.tasks.length const percent = total === 0 ? 0 : Math.round((completed / total) * 100) const allDone = total > 0 && completed === total return html` <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', !this.open && 'pointer-events-none translate-y-1 opacity-0', )} > <uip-card> <uip-card-header> <uip-card-title>Get started</uip-card-title> <uip-card-description>Finish these steps to unlock everything your workspace can do.</uip-card-description> </uip-card-header> <uip-card-content 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> <uip-progress value=${percent}></uip-progress> </div> ${allDone ? html` <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" > ${icon(Check, 'check', 'size-8')} </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> ` : html` <div class="-mx-2 divide-y"> ${this.tasks.map( (task) => html` <button type="button" role="checkbox" aria-checked=${task.done ? 'true' : 'false'} @click=${() => this.toggle(task.id)} 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" > ${task.done ? html`<span class="bg-primary text-primary-foreground flex size-9 shrink-0 items-center justify-center rounded-md shadow-xs" > ${icon(Check, 'check', 'size-4')} </span>` : html`<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" > ${this.taskIcon(task.id, 'size-4')} </span>`} <span class="min-w-0 flex-1"> <span class=${cn('block truncate text-sm font-medium', task.done && 'text-muted-foreground')}> ${task.heading} </span> <span class="text-muted-foreground mt-0.5 block truncate text-xs" >${task.description}</span > </span> ${icon(ChevronRight, 'chevron-right', 'text-muted-foreground/50 size-4 shrink-0 transition-transform duration-200 group-hover:translate-x-0.5')} </button> `, )} </div> `} </uip-card-content> <uip-card-footer class="justify-between"> ${!allDone ? html`<uip-button variant="ghost" size="sm" @click=${() => { this.open = false }} >Skip for now</uip-button >` : html`<span aria-hidden="true"></span>`} <button type="button" aria-label="Dismiss checklist" @click=${() => { this.open = false }} class="hover:bg-foreground/10 focus-visible:ring-ring rounded-md p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none" > ${icon(X, 'x', 'text-muted-foreground size-4')} </button> </uip-card-footer> </uip-card> </div> ` } } customElements.get('uip-onboarding-checklist') || customElements.define('uip-onboarding-checklist', UipOnboardingChecklist) declare global { interface HTMLElementTagNameMap { 'uip-onboarding-checklist': UipOnboardingChecklist } }
Raw manifest:https://uipkge.dev/r/lit/onboarding-checklist.json