Onboarding

Composable multi-step onboarding for React and shadcn/ui with a step indicator, feature carousel, choice group, and tips list.

Updated

Onboarding is a set of composable React primitives for shadcn/ui. Use it for signup, first-run setup, or account personalization screens in SaaS apps.

Installation

pnpm dlx shadcn@latest add @cult-ui/onboarding

Usage

import { Onboarding, FeatureCarousel, ChoiceGroup, TipsList, useOnboarding, } from "@/components/ui/onboarding";
<Onboarding totalSteps={3} onComplete={() => console.log("done")}> <Onboarding.StepIndicator /> <Onboarding.Step step={1}> <Onboarding.Header title="Welcome" description="Here's what you can do" /> </Onboarding.Step> <Onboarding.Step step={2}> <Onboarding.Header title="Personalize" description="Choose your role" /> <ChoiceGroup name="role" onValueChange={(v) => setRole(v)}> <ChoiceGroup.Item value="designer">Designer</ChoiceGroup.Item> <ChoiceGroup.Item value="developer">Developer</ChoiceGroup.Item> </ChoiceGroup> </Onboarding.Step> <Onboarding.Step step={3}> <Onboarding.Header title="You're ready!" description="A few tips" /> <TipsList title="Tips"> <TipsList.Item number={1}>Be specific with your prompts.</TipsList.Item> </TipsList> </Onboarding.Step> <Onboarding.Navigation completeLabel="Get Started" /> </Onboarding>

Components

Onboarding (Root)

The root component manages step state and provides context to all children.

PropTypeDefaultDescription
totalStepsnumber—Total number of steps (required)
valuenumber—Controlled current step (1-based)
defaultValuenumber1Default step for uncontrolled usage
onValueChange(step: number) => void—Callback when step changes
maxStepValuenumber0Max sub-step index for step 1 (e.g. feature carousel)
canGoNext(step, stepValue) => boolean() => trueCustom gate for proceeding
onComplete() => void—Called when the last step is completed

Onboarding.Step

Renders children only when its step matches the current step.

PropTypeDescription
stepnumberStep index (1-based)

Onboarding.Header

Renders a centered title and description with serif font styling.

PropTypeDescription
titlestringStep title
descriptionstringStep description
childrenReactNodeCustom content (overrides title/description)

Onboarding.Navigation

Default navigation with Back and Next/Complete buttons.

PropTypeDefaultDescription
backLabelstring"Back"Back button label
nextLabelstring"Next"Next button label
completeLabelstring"Start Creating"Complete button label
canGoNextboolean—Override next gate from context
childrenReactNode—Custom navigation content

Onboarding.StepIndicator

Connects to onboarding context and renders a StepIndicator.

PropTypeDefaultDescription
variant"dots" | "pills""dots"Visual style of the indicators
dotClassNamestring—Extra class for each step dot

FeatureCarousel

Tablist-style feature selector that syncs with stepValue in the onboarding context.

PropTypeDefaultDescription
valuenumber—Controlled active index
defaultValuenumber0Default active index
onValueChange(index: number) => void—Callback on change
totalItemsnumber—Total items (derived from children if omitted)

FeatureCarousel.Item

PropTypeDescription
indexnumberIndex of this item (0-based)

ChoiceGroup

Accessible radio group for single-select choices.

PropTypeDefaultDescription
namestring—Radio group name (required for a11y)
valuestring | null—Controlled selected value
defaultValuestring | nullnullDefault selected value
onValueChange(value: string) => void—Callback on selection
orientation"horizontal" | "vertical" | "grid""grid"Layout orientation

ChoiceGroup.Item

PropTypeDescription
valuestringValue when this item is selected

TipsList

Ordered tips list with optional visible title.

PropTypeDescription
titlestringOptional label (screen-reader only by default)

TipsList.Item

PropTypeDescription
numbernumberOptional step number shown inline

Controlled Usage

const [step, setStep] = useState(1) <Onboarding value={step} onValueChange={setStep} totalSteps={3} canGoNext={(s) => s !== 2 || roleSelected} onComplete={handleComplete} > {/* steps */} </Onboarding>

Pair maxStepValue on the root with a FeatureCarousel synced via stepValue to let users page through features before advancing:

const FEATURES = ["Generate", "Ideas", "Upload"] <Onboarding totalSteps={3} maxStepValue={FEATURES.length - 1}> <Onboarding.Step step={1}> {({ stepValue, setStepValue }) => ( <FeatureCarousel value={stepValue} onValueChange={setStepValue} totalItems={FEATURES.length} > {FEATURES.map((f, i) => ( <FeatureCarousel.Item key={f} index={i}> {f} </FeatureCarousel.Item> ))} </FeatureCarousel> )} </Onboarding.Step> </Onboarding>