Onboarding
First-run UI that stays calm: a skippable step flow with live vignettes, a setup checklist, and a capability tour anchored to real controls.
On this page
Installation
Usage
import { Onboarding } from "@/components/ui/chatcn/onboarding"
<Onboarding
steps={[
{
id: "ask",
title: "Ask in plain words",
description: "Describe the bug or the change you want.",
media: <ComposerVignette />,
action: "Show me projects",
},
{ id: "projects", title: "Projects keep the context", description: "…" },
]}
onComplete={markOnboarded}
onSkip={markOnboarded}
/>Give each step an action that names what comes next. It reads better than a row of Next buttons, and the last step defaults to Start a chat. Build media from your own UI so the tour looks like the product it introduces.
Examples
Inline
variant="inline" renders the same flow as a card, for an empty state that should not interrupt with a dialog.
Checklist
OnboardingChecklist lists first-run tasks with a progress bar. Mark tasks done as the app sees them happen; each row's check settles in with a short blur crossfade. It collapses to its header and can be dismissed.
Capability tour
OnboardingTour points at real controls: a selector, an element, a ref or a function. One coach mark at a time, a hairline ring around the target and nothing dimmed, so the app stays readable. Escape skips; clicking elsewhere leaves the tour where it is.
Props
Onboarding
| Prop | Type | Notes |
|---|---|---|
steps | OnboardingStep[] | { id, title, description, media?, action? } |
variant | "dialog" | "inline" | Default dialog |
open, defaultOpen, onOpenChange | boolean | Dialog only. Opens by default |
step, defaultStep, onStepChange | number | Zero-based, controlled or uncontrolled |
onComplete | () => void | The forward button on the last step |
onSkip | () => void | Skip tour, or Escape in the dialog |
label | string | Accessible name. Default Welcome |
completeLabel, nextLabel, backLabel, skipLabel | string | Button copy |
container | HTMLElement | Portal container for the dialog |
OnboardingChecklist
| Prop | Type | Notes |
|---|---|---|
tasks | OnboardingTask[] | { id, title, description?, done?, action?: { label, onClick } } |
title, doneTitle | string | Defaults Get set up and You are all set |
onDismiss | () => void | Shows the dismiss button |
open, defaultOpen, onOpenChange | boolean | Expanded state |
OnboardingTour
| Prop | Type | Notes |
|---|---|---|
steps | OnboardingTourStep[] | { id, target, title, description, side?, align? } |
open, defaultOpen, onOpenChange | boolean | Starts open |
step, defaultStep, onStepChange | number | Controlled or uncontrolled |
onComplete, onSkip | () => void | Done on the last step, or Skip and Escape |
doneLabel, nextLabel, backLabel, skipLabel | string | Button copy |
container | HTMLElement | Portal container for the coach mark and ring |
Keyboard
| Key | Action |
|---|---|
ArrowRight, ArrowLeft | Next or previous step, mirrored in right-to-left layouts |
Enter | The forward action when the flow or coach mark has focus |
Escape | Skip the dialog or the tour |
Tab | Move between Skip, Back and the forward button |
Accessibility
- Focus starts on the flow itself, not on a button, so arrow keys and Enter work at once and no focus ring greets a pointer user.
- A polite live region announces each change: Step 2 of 4, then the step title.
- Coach marks are labelled by their title and announce their position, for example Step 1 of 3.
- Checklist rows say done or to do after the title. Progress is a
progressbarwith its value, not only a bar.
Motion
- With the pointer, step content crossfades with 8 px of travel in the direction of travel: 220 ms in, 160 ms out with half the travel. Arrow keys and Enter change steps instantly.
- Coach marks grow from the target side over 180 ms. The ring fades in over 150 ms and never pulses.
- No confetti and no spotlight. Under reduced motion, only opacity changes.