Skip to content
ShellOnboardingMenu

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.

Four calm steps with live vignettes. Skippable from every step.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/onboarding.json

Usage

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

The same flow as a card in the empty state, without 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.

First-run tasks that check themselves off, with progress.

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.

Coach marks anchored to real controls, with a hairline ring and nothing dimmed.

Props

Onboarding

PropTypeNotes
stepsOnboardingStep[]{ id, title, description, media?, action? }
variant"dialog" | "inline"Default dialog
open, defaultOpen, onOpenChangebooleanDialog only. Opens by default
step, defaultStep, onStepChangenumberZero-based, controlled or uncontrolled
onComplete() => voidThe forward button on the last step
onSkip() => voidSkip tour, or Escape in the dialog
labelstringAccessible name. Default Welcome
completeLabel, nextLabel, backLabel, skipLabelstringButton copy
containerHTMLElementPortal container for the dialog

OnboardingChecklist

PropTypeNotes
tasksOnboardingTask[]{ id, title, description?, done?, action?: { label, onClick } }
title, doneTitlestringDefaults Get set up and You are all set
onDismiss() => voidShows the dismiss button
open, defaultOpen, onOpenChangebooleanExpanded state

OnboardingTour

PropTypeNotes
stepsOnboardingTourStep[]{ id, target, title, description, side?, align? }
open, defaultOpen, onOpenChangebooleanStarts open
step, defaultStep, onStepChangenumberControlled or uncontrolled
onComplete, onSkip() => voidDone on the last step, or Skip and Escape
doneLabel, nextLabel, backLabel, skipLabelstringButton copy
containerHTMLElementPortal container for the coach mark and ring

Keyboard

KeyAction
ArrowRight, ArrowLeftNext or previous step, mirrored in right-to-left layouts
EnterThe forward action when the flow or coach mark has focus
EscapeSkip the dialog or the tour
TabMove 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 progressbar with 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.