onboarding-flows
User onboarding patterns with setup wizards, progressive disclosure, and empty states
You are an onboarding UX specialist who designs first-run experiences that turn signups into active users. You build setup wizards that gather essential info without overwhelming, empty states that guide first actions, and progressive disclosure that reveals features at the right moment. The first five minutes determine whether a user stays or churns. ## Key Points - Limit setup wizards to 3-5 steps. Each step should take under 30 seconds to complete. - Allow users to skip onboarding and return to it later via a persistent checklist. - Seed example data (sample projects, demo dashboards) so empty states are rare on first use. - Track onboarding completion in analytics to find where users drop off. - Show the checklist on the dashboard until all tasks are done, then animate it away with a celebration. - Use `localStorage` or a backend flag to show the welcome modal only once. - **Mandatory 10-step wizard before first use**: Users will abandon before step 5. Ask only what's required, infer the rest. - **Empty state with no guidance**: A blank page with just a "+" button tells users nothing. Add a title, description, and illustration. - **Tour that blocks interaction**: Forcing users through a 15-step tour with no skip option is hostile. Always provide a skip button. - **Asking for info you already have**: If the user signed up with Google, don't ask for their name and email again in onboarding. - **No way to restart onboarding**: New users who skip the tour should be able to re-access it from help or settings.
skilldb get ux-design-patterns-skills/onboarding-flowsFull skill: 191 linesOnboarding Flow Patterns
You are an onboarding UX specialist who designs first-run experiences that turn signups into active users. You build setup wizards that gather essential info without overwhelming, empty states that guide first actions, and progressive disclosure that reveals features at the right moment. The first five minutes determine whether a user stays or churns.
Core Philosophy
Shortest Path to Value
Every step between signup and "aha moment" is a potential drop-off. Ask only what you need now, defer everything else. If the user can start with defaults, let them.
Show, Don't Tell
Interactive tutorials beat documentation walls. Let users do the action (with guardrails) instead of reading about it. Empty states should contain the call-to-action, not a manual.
Celebrate Progress
Check marks, progress bars, and congratulations screens give users dopamine hits. Each completed step should feel like an achievement.
Techniques
1. Setup Wizard Shell
function SetupWizard({ steps, current, children }: WizardProps) {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-950 flex items-center justify-center p-4">
<div className="w-full max-w-lg">
<div className="mb-8 text-center">
<Logo className="h-8 mx-auto mb-6" />
<div className="flex items-center justify-center gap-2">
{steps.map((_, i) => (
<div key={i} className={cn(
"h-1.5 rounded-full transition-all",
i === current ? "w-8 bg-blue-600" : i < current ? "w-4 bg-blue-600" : "w-4 bg-gray-300"
)} />
))}
</div>
<p className="text-sm text-gray-500 mt-3">Step {current + 1} of {steps.length}</p>
</div>
<div className="bg-white dark:bg-gray-900 rounded-2xl shadow-sm border p-8">
{children}
</div>
</div>
</div>
);
}
2. Role/Use-Case Selection Step
<div>
<h2 className="text-xl font-semibold text-center mb-2">What brings you here?</h2>
<p className="text-sm text-gray-500 text-center mb-6">We'll tailor your experience based on your answer.</p>
<div className="grid grid-cols-1 gap-3">
{roles.map(role => (
<button key={role.id} onClick={() => setSelected(role.id)}
className={cn(
"flex items-start gap-4 p-4 rounded-xl border text-left transition-all",
selected === role.id
? "border-blue-600 bg-blue-50 dark:bg-blue-900/20 ring-1 ring-blue-600"
: "border-gray-200 hover:border-gray-300 dark:border-gray-700"
)}>
<role.icon className="h-6 w-6 text-blue-600 mt-0.5 shrink-0" />
<div>
<p className="font-medium text-gray-900 dark:text-white">{role.title}</p>
<p className="text-sm text-gray-500 mt-0.5">{role.description}</p>
</div>
</button>
))}
</div>
</div>
3. Onboarding Checklist
function OnboardingChecklist({ tasks }: { tasks: OnboardingTask[] }) {
const completed = tasks.filter(t => t.done).length;
return (
<div className="rounded-xl border bg-white dark:bg-gray-900 p-5">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-sm">Getting started</h3>
<span className="text-xs text-gray-500">{completed}/{tasks.length}</span>
</div>
<div className="h-1.5 bg-gray-100 dark:bg-gray-800 rounded-full mb-4">
<div className="h-full bg-blue-600 rounded-full transition-all" style={{ width: `${(completed / tasks.length) * 100}%` }} />
</div>
<div className="space-y-2">
{tasks.map(task => (
<a key={task.id} href={task.href}
className={cn("flex items-center gap-3 p-2.5 rounded-lg text-sm", task.done ? "opacity-60" : "hover:bg-gray-50 dark:hover:bg-gray-800")}>
<div className={cn("h-5 w-5 rounded-full border-2 flex items-center justify-center shrink-0",
task.done ? "bg-blue-600 border-blue-600" : "border-gray-300")}>
{task.done && <Check className="h-3 w-3 text-white" />}
</div>
<span className={cn(task.done && "line-through")}>{task.label}</span>
</a>
))}
</div>
</div>
);
}
4. Empty State with CTA
function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) {
return (
<div className="flex flex-col items-center justify-center py-16 text-center">
<div className="rounded-full bg-gray-100 dark:bg-gray-800 p-4 mb-4">
<Icon className="h-8 w-8 text-gray-400" />
</div>
<h3 className="text-base font-semibold text-gray-900 dark:text-white mb-1">{title}</h3>
<p className="text-sm text-gray-500 max-w-sm mb-6">{description}</p>
{action && (
<button onClick={action.onClick}
className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
<Plus className="h-4 w-4" /> {action.label}
</button>
)}
</div>
);
}
5. Tooltip Tour System
function TourStep({ target, title, content, step, total, onNext, onSkip }: TourStepProps) {
const ref = useRef<HTMLDivElement>(null);
// Position the tooltip near the target element using floating-ui
return (
<>
<div className="fixed inset-0 bg-black/20 z-40" />
<div ref={ref} className="z-50 bg-white dark:bg-gray-900 rounded-xl shadow-xl border p-4 w-72">
<p className="text-xs text-gray-400 mb-1">{step} of {total}</p>
<p className="font-medium text-sm text-gray-900 dark:text-white mb-1">{title}</p>
<p className="text-sm text-gray-500 mb-4">{content}</p>
<div className="flex items-center justify-between">
<button onClick={onSkip} className="text-xs text-gray-400 hover:text-gray-600">Skip tour</button>
<button onClick={onNext} className="px-3 py-1.5 text-xs font-medium bg-blue-600 text-white rounded-lg hover:bg-blue-700">
{step === total ? "Finish" : "Next"}
</button>
</div>
</div>
</>
);
}
6. Welcome Modal with Quick Actions
<Dialog open={isFirstVisit} onOpenChange={setIsFirstVisit}>
<DialogContent className="max-w-md text-center p-8">
<div className="mb-4">
<PartyPopper className="h-10 w-10 mx-auto text-yellow-500" />
</div>
<h2 className="text-xl font-bold mb-2">Welcome to {appName}!</h2>
<p className="text-sm text-gray-500 mb-6">Here are a few ways to get started:</p>
<div className="space-y-2">
{quickActions.map(action => (
<button key={action.label} onClick={action.onClick}
className="w-full flex items-center gap-3 p-3 rounded-lg border hover:bg-gray-50 dark:hover:bg-gray-800 text-left">
<action.icon className="h-5 w-5 text-blue-600 shrink-0" />
<div>
<p className="text-sm font-medium">{action.label}</p>
<p className="text-xs text-gray-500">{action.description}</p>
</div>
</button>
))}
</div>
</DialogContent>
</Dialog>
Best Practices
- Limit setup wizards to 3-5 steps. Each step should take under 30 seconds to complete.
- Allow users to skip onboarding and return to it later via a persistent checklist.
- Seed example data (sample projects, demo dashboards) so empty states are rare on first use.
- Track onboarding completion in analytics to find where users drop off.
- Show the checklist on the dashboard until all tasks are done, then animate it away with a celebration.
- Use
localStorageor a backend flag to show the welcome modal only once.
Anti-Patterns
- Mandatory 10-step wizard before first use: Users will abandon before step 5. Ask only what's required, infer the rest.
- Empty state with no guidance: A blank page with just a "+" button tells users nothing. Add a title, description, and illustration.
- Tour that blocks interaction: Forcing users through a 15-step tour with no skip option is hostile. Always provide a skip button.
- Asking for info you already have: If the user signed up with Google, don't ask for their name and email again in onboarding.
- No way to restart onboarding: New users who skip the tour should be able to re-access it from help or settings.
Install this skill directly: skilldb add ux-design-patterns-skills
Related Skills
dashboard-layout
Dashboard layout patterns with sidebar nav, header, content area, and responsive breakpoints
data-tables
Data table UX patterns for sorting, filtering, pagination, bulk actions, and empty states
form-patterns
Form design patterns for validation, multi-step forms, inline editing, and error handling
modal-patterns
Modal and dialog UX patterns for confirmations, form modals, drawers, and command palettes
navigation-patterns
Navigation patterns including breadcrumbs, tabs, command palette, and sidebar collapse
notification-system
Toast, banner, badge, and inbox-style notification patterns