# Onboarding Flow

> A React onboarding flow for a marketplace app: welcome slides, notification and location asks, a short profile, preferences and a finish.

- Type: React block (Blocks)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.2
- Install: `npx wingo-ui@latest add onboarding-flow`
- Docs: https://wingo-ui.com/components/onboarding-flow

## Interaction

Swipe or drag the three welcome slides, use the dots, the arrow keys or Continue; Skip on the slides and Get started on the last one go to the permissions. From there a header shows a back button, a three part progress bar and Skip, which jumps straight to the finish with what you entered so far; every step change slides in from the side it belongs to, moves focus to the step title and is announced ("Step 2 of 4: Permissions"). Permissions: Allow opens the real browser prompt for notifications or location (with simulatePermissions it answers after 600 ms), Not now skips it; each card then shows Allowed, Denied with where to change it, Skipped with Allow anyway, or why the browser cannot do it. Continue is always enabled. A granted location fills the city with the nearest one. Profile: tap the photo to choose, crop or take a picture (optional), type the name, search the city (a full height sheet on phones), and tap interest chips, which are toggle buttons; the counter turns green at the minimum. Continue with a missing name or too few interests shows the errors and focuses the first. Preferences: pick Instantly, Daily or Weekly, toggle the newsletter card and read the summary line update. Finish celebrates with a bouncy check and one ring pulse (and a success haptic on phones), then Go to Home finishes and Start over starts over. Everything you enter is saved as you go, so a reload resumes on the same step with a short "Picking up where you left off" note. On laptops a picture beside the card cross fades per step and per slide; on phones the flow fills the screen with the main button pinned above the safe area. With reduced motion steps cross fade and the check simply appears.

## Installation

```bash
npx wingo-ui@latest add onboarding-flow
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/onboarding-flow` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/blocks/onboarding-flow.tsx`
- `components/blocks/onboarding-flow-data.ts`

npm dependencies: `npm i motion lucide-react react-hook-form zod`

Wingo UI dependencies (installed automatically):

- [Alert](https://wingo-ui.com/components/alert) ([Markdown](https://wingo-ui.com/components/alert.md))
- [Avatar Upload](https://wingo-ui.com/components/avatar-upload) ([Markdown](https://wingo-ui.com/components/avatar-upload.md))
- [Button](https://wingo-ui.com/components/button) ([Markdown](https://wingo-ui.com/components/button.md))
- [Combobox](https://wingo-ui.com/components/combobox) ([Markdown](https://wingo-ui.com/components/combobox.md))
- [Image Cropper](https://wingo-ui.com/components/image-cropper) ([Markdown](https://wingo-ui.com/components/image-cropper.md))
- [Input](https://wingo-ui.com/components/input) ([Markdown](https://wingo-ui.com/components/input.md))
- [Onboarding Carousel](https://wingo-ui.com/components/onboarding-carousel) ([Markdown](https://wingo-ui.com/components/onboarding-carousel.md))
- [Progress](https://wingo-ui.com/components/progress) ([Markdown](https://wingo-ui.com/components/progress.md))
- [Segmented Control](https://wingo-ui.com/components/segmented-control) ([Markdown](https://wingo-ui.com/components/segmented-control.md))
- [Switch](https://wingo-ui.com/components/switch) ([Markdown](https://wingo-ui.com/components/switch.md))
- [Tag](https://wingo-ui.com/components/tag) ([Markdown](https://wingo-ui.com/components/tag.md))
- [Toast](https://wingo-ui.com/components/toast) ([Markdown](https://wingo-ui.com/components/toast.md))
- [usePersistedState](https://wingo-ui.com/components/use-persisted-state) ([Markdown](https://wingo-ui.com/components/use-persisted-state.md))
- [Commerce](https://wingo-ui.com/components/commerce) ([Markdown](https://wingo-ui.com/components/commerce.md))
- [Form validation](https://wingo-ui.com/components/form-validation) ([Markdown](https://wingo-ui.com/components/form-validation.md))
- [Haptics](https://wingo-ui.com/components/haptics) ([Markdown](https://wingo-ui.com/components/haptics.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Romanian locations](https://wingo-ui.com/components/ro-locations) ([Markdown](https://wingo-ui.com/components/ro-locations.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
// app/(app)/welcome/page.tsx
"use client";

import { useRouter } from "next/navigation";
import { OnboardingFlow } from "@/components/blocks/onboarding-flow";

export default function Page() {
  const router = useRouter();
  return (
    <OnboardingFlow
      user={{ name: "Emma Carter" }}
      persistKey="onboarding:ana"
      onUpload={async (blob) => {
        const response = await fetch("/api/avatar", { method: "POST", body: blob });
        if (!response.ok) throw new Error("Upload failed");
        return (await response.json()).url;
      }}
      onComplete={async ({ profile, preferences, permissions }) => {
        await fetch("/api/onboarding", { method: "POST", body: JSON.stringify({ profile, preferences, permissions }) });
        router.push("/");
      }}
    />
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultStep?` | `OnboardingStep` | `"welcome"` | The step shown first (uncontrolled, and the fallback before storage is read): "welcome" \| "permissions" \| "profile" \| "preferences" \| "done". |
| `step?` | `OnboardingStep` |  | The current step, controlled (then nothing is restored from storage for it). |
| `onStepChange?` | `(step: OnboardingStep) => void` |  | Runs on every step change (mirror it to the URL). |
| `slides?` | `OnboardingSlide[]` |  | The welcome slides (onboarding-carousel's slide shape). Default: the three Acme Market slides from ONBOARDING_FLOW_SAMPLE with their vignettes and the side panel pictures; custom slides show their image in the side panel. |
| `interests?` | `OnboardingInterest[]` |  | The chips of the profile step: { id, label, icon, description }. Default: the 12 Acme Market categories of ONBOARDING_FLOW_SAMPLE.interests; icon takes a node or a sample icon name. |
| `minInterests?` | `number` | `3` | How many interests the profile needs; 0 makes them optional. |
| `permissions?` | `("notifications" \| "location")[]` |  | The permissions asked for, default both; [] skips the step. |
| `simulatePermissions?` | `boolean` | `false` | Answer granted after 600 ms instead of calling the browser (demos, tests); a simulated location is Gheorgheni, Cluj-Napoca. |
| `user?` | `OnboardingUser` |  | The signed-up person ({ name, avatar, city }): the name the profile starts with and the side panel's profile card. Default ONBOARDING_FLOW_SAMPLE.user (Emma Carter). |
| `defaultProfile?` | `OnboardingProfile` |  | The profile the flow starts with ({ avatar, name, city, interests }); default empty with the name from user. |
| `profile?` | `OnboardingProfile` |  | The profile, controlled; onProfileChange gets every draft change. |
| `onProfileChange?` | `(profile: OnboardingProfile) => void` |  | Every change of the profile draft (photo, name, city, interests). |
| `defaultPreferences?` | `OnboardingPreferences` |  | The preferences the flow starts with; default { frequency: "daily", newsletter: false }. |
| `preferences?` | `OnboardingPreferences` |  | The preferences, controlled; onPreferencesChange gets every change. |
| `onPreferencesChange?` | `(preferences: OnboardingPreferences) => void` |  | Every change of the frequency or the newsletter switch. |
| `persistKey?` | `string` | `"onboarding-flow"` | Local storage key that keeps the step, the drafts and the permission answers, so a closed app resumes where it stopped; empty keeps them in memory. |
| `showSkip?` | `boolean` | `true` | Skip on the slides and in the header of the later steps. |
| `allowRestart?` | `boolean` | `true` | The Start over button on the finish screen, which clears the saved progress. |
| `art?` | `"image" \| "none"` | `"image"` | The picture beside the card from 1024px, cross fading per step and per slide (tablets show the card alone, centered); none centers the card alone everywhere. |
| `onComplete?` | `(result: OnboardingFlowResult) => (void \| Promise<unknown>)` |  | Go to Home: gets { profile, preferences, permissions } (each permission granted, denied or skipped); a promise spins the button. Default: the toast "Welcome to Acme Market". |
| `onUpload?` | `(blob: Blob, context: AvatarUploadContext) => Promise<string \| void>` |  | Uploads the cropped photo and resolves its url. Default: simulated for 900 ms, then kept as a data url; a failed upload shows a note and the flow continues without a photo. |
| `tone?` | `Tone` | `"neutral"` | The primary buttons, the progress bar, the selected chips, the frequency thumb, the switch and the slides' button and dots. |
| `radius?` | `"sm" \| "md" \| "lg"` | `"md"` | The card and the side panel, the permission tiles and the controls' corners. |
| `loading?` | `boolean` | `false` | The card as skeletons of the profile step and a pulsing side panel. |
| `animateIn?` | `boolean` | `true` | The side panel and the card rise in once when the page mounts on the client (server html is already final). |
| `labels?` | `Partial<OnboardingFlowLabels>` |  | Every visible string: DEFAULT_ONBOARDING_FLOW_LABELS (English) by default; carousel, avatarUpload, cropper and validation carry the composed items' strings. |
| `locale?` | `string` | `"en-US"` | Prices and distances on the slides and the page language (lang). |
| `classNames?` | `Partial<Record<OnboardingFlowSlot, string>>` |  | Classes for art, main, card, header, progress, step, title, description, footer, permission, interests and done. |

## Parts

### DEFAULT_ONBOARDING_FLOW_LABELS (constant)

The English strings for every visible label, the default of labels. Type: OnboardingFlowLabels.

### ONBOARDING_FLOW_LABELS_EN (constant)

The same English strings under their older name, kept for existing imports.

### ONBOARDING_FLOW_SAMPLE (constant)

The demo data: the user, a simulated position, the slides, interests, nearby listings, a sample posting and the art.

### OnboardingFlowProps (type)

Props of OnboardingFlow, including the div attributes of the root.

### OnboardingFlowResult (type)

What onComplete receives: { profile, preferences, permissions }.

### OnboardingStep (type)

"welcome" | "permissions" | "profile" | "preferences" | "done", the values of step and defaultStep.

## Examples

- **Permissions**: Notifications and location with the reason for each; Allow answers after 600 ms here, Not now skips.
- **Profile, too few interests**: Two interests picked out of the three needed: the counter, the error and the focus on the chips.
- **Done**: The finish: a bouncy check with one ring pulse, three tips, Go to Home and Start over.
- **Brand tone**: tone="brand" on the buttons, the progress, the chips, the frequency thumb and the switch.
- **British English**: The default labels with locale en-GB, from the first slide.
- **No picture, no permissions**: art="none" centers the card alone and permissions={[]} leaves the step out.

## See also

- [Onboarding Carousel](https://wingo-ui.com/components/onboarding-carousel) ([Markdown](https://wingo-ui.com/components/onboarding-carousel.md))
- [Avatar Upload](https://wingo-ui.com/components/avatar-upload) ([Markdown](https://wingo-ui.com/components/avatar-upload.md))
- [Steps](https://wingo-ui.com/components/steps) ([Markdown](https://wingo-ui.com/components/steps.md))
- [Register Page](https://wingo-ui.com/components/register-page) ([Markdown](https://wingo-ui.com/components/register-page.md))
- [Mobile App Home](https://wingo-ui.com/components/mobile-app-home) ([Markdown](https://wingo-ui.com/components/mobile-app-home.md))

---

Canonical: https://wingo-ui.com/components/onboarding-flow
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
