Intro Disclosure
InteractiveA first-run onboarding sequence: a stepped dialog card with title, body, optional media, Back/Next, a clickable progress-dots row, and Skip, where steps cross-fade and slide in the travel direction while the card height springs to fit the incoming content. It is a real role="dialog" with focus trap, Escape-to-skip, and focus return; under prefers-reduced-motion the slide, blur, and height morph are dropped and steps swap instantly.
Inspired by Cult UI — Intro Disclosure / Onboarding (free, MIT) — independently built, not affiliated. Attribution policy
Installation
npx shadcn@latest add https://parable-three.vercel.app/r/intro-disclosure.jsonRuns the shadcn CLI — the component source is copied intocomponents/parable/intro-disclosure.tsx. You own it from there.
Usage
The exact code rendering the preview above.
<div className="relative flex h-[440px] w-full max-w-[380px] items-center justify-center overflow-hidden rounded-2xl border border-white/10 bg-[#0f0f10]">
<div
aria-hidden
className="pointer-events-none absolute inset-0"
style={{
backgroundImage:
"radial-gradient(circle at 28% 18%, rgba(139,92,246,0.20), transparent 55%), radial-gradient(circle at 82% 84%, rgba(34,211,238,0.14), transparent 55%)",
}}
/>
<IntroDisclosure
defaultOpen
steps={[
{
title: "Welcome to Parable",
body: "A living registry of motion-first React components — copy one file into your app and own it outright.",
media: (
<div className="flex h-28 items-center justify-center bg-gradient-to-br from-violet-500/30 via-fuchsia-500/20 to-transparent">
<Sparkles className="text-violet-200" />
</div>
),
},
{
title: "Themed to your brand",
body: "Every component ships with --pb-* tokens, so accent and surface colours follow your own design system.",
media: (
<div className="flex h-28 items-center justify-center bg-gradient-to-br from-fuchsia-500/30 via-amber-400/15 to-transparent">
<Palette className="text-fuchsia-200" />
</div>
),
},
{
title: "Ship in minutes",
body: "Reduced-motion safe, keyboard operable, and SSR-ready out of the box. Press Get started to finish the tour.",
media: (
<div className="flex h-28 items-center justify-center bg-gradient-to-br from-cyan-400/25 via-violet-500/20 to-transparent">
<Rocket className="text-cyan-200" />
</div>
),
},
]}
/>
</div>Props
IntroDisclosureProps extends Omit< React.HTMLAttributes<HTMLDivElement>, | "children" | "title" | "onAnimationStart" | "onAnimationEnd" | "onAnimationIteration" | "onDrag" | "onDragStart" | "onDragEnd" >
| Prop | Type | Default | Description |
|---|---|---|---|
| steps* | IntroStep[] | — | Ordered steps the tour walks through. |
| open | boolean | — | Controlled open state. Pair with `onOpenChange`. |
| defaultOpen | boolean | false | Uncontrolled initial open state. |
| onOpenChange | (open: boolean) => void | — | Fires with the next open state on Skip, Escape, backdrop, or completion. |
| step | number | — | Controlled active step index. Pair with `onStepChange`. |
| defaultStep | number | 0 | Uncontrolled initial step index. |
| onStepChange | (step: number) => void | — | Fires with the next step index on Back / Next / dot navigation. |
| onComplete | () => void | — | Fires when the primary button is pressed on the final step. |
| accent | string | "#8b5cf6" | Accent hex — primary button, active dot, focus ring. Mirrors `--pb-violet`. |
| background | string | "#0f0f10" | Card surface colour. Mirrors the site `--pb-ink` token. |
| nextLabel | React.ReactNode | "Next" | Label for the advance button on non-final steps. |
| backLabel | React.ReactNode | "Back" | Label for the back button. |
| skipLabel | React.ReactNode | "Skip" | Label for the dismiss button. |
| doneLabel | React.ReactNode | "Get started" | Label for the primary button on the final step. |
| className | string | — | Extra classes on the dialog card. |
npm dependencies
- motion