# Onboarding Shell

> An animated multi-step onboarding frame for React and shadcn, with segmented progress, Back, Skip and a live preview slot, on a headless step engine.

One decision per screen, in a frame that keeps score. Segments fill as you move, steps leave against the direction of travel before the next arrives, Enter continues and Escape goes back, and focus lands on each new heading while a live region names the step.

- Tier: Eased Pro
- Interaction: Press
- Page: https://eased.dev/components/onboarding-shell
- Installs: motion

## Install

Onboarding Shell ships with Eased Pro (https://eased.dev/#pricing). Members add the `@eased-pro` registry with their token (https://eased.dev/account), then run:

```bash
npx shadcn add @eased-pro/onboarding-shell
```

The frame for a multi-step onboarding flow: segmented progress, one decision per screen, Back, Skip and the primary action, and an optional live preview beside it. It draws a flow from `useSteps`, the headless step engine in Onboarding core.

## When to use

- A first-run flow of three to six questions: profile, workspace, team, preferences.
- Any flow where each screen asks one thing and the answers build on each other.
- When you want the step logic (validity, branching, skip, async steps, saved progress) without writing it, and the keyboard and screen reader behaviour done.

## When not to use

- A checklist people come back to over days: use [Onboarding Checklist](/components/onboarding-checklist).
- One form that fits on a screen: a plain form with Field is faster to fill.

## Usage

```tsx example.tsx
import { useSteps } from "@/components/eased/_primitives/onboarding-core/onboarding-core";
import { OnboardingShell } from "@/components/eased/onboarding-shell/onboarding-shell";
import { Field } from "@/components/eased/field/field";
import { Input } from "@/components/eased/input/input";

export function ProfileSetup() {
  const flow = useSteps({
    steps: [
      { id: "name", title: "What should your team call you?", valid: (p) => p.name.trim() !== "" },
      { id: "role", title: "What’s your role?", optional: true },
    ],
    initialData: { name: "", role: "" },
    onComplete: (profile) => saveProfile(profile),
  });

  return (
    <OnboardingShell
      flow={flow}
      done={<h2>You’re set, {flow.data.name}</h2>}
      steps={{
        name: (
          <Field label="Full name">
            <Input value={flow.data.name} onChange={(e) => flow.setData({ name: e.target.value })} />
          </Field>
        ),
        role: <RolePicker value={flow.data.role} onChange={(role) => flow.setData({ role })} />,
      }}
    />
  );
}
```

## Examples

### A live preview

Pass `preview` to show what is being set up beside the steps once the shell is 48rem wide, and `compactPreview` for a smaller version above the heading below that. [Workspace Setup](/components/workspace-setup) is the full example.

```tsx
<OnboardingShell flow={flow} preview={<AppPreview draft={flow.data} focus={flow.id} />} compactPreview={<Badge draft={flow.data} />}>
```

### An async step

`onNext` on a step runs before the move. Return a promise and the primary button shows a spinner; throw and the step stays open with the message above the footer.

```tsx
{ id: "invite", title: "Invite your team", onNext: (d) => api.invite(d.emails) }
```

### Saved progress

```tsx
useSteps({ steps, initialData, persist: { key: "kestrel:profile" } });
```

## API reference

### OnboardingShell

| Prop | Type | Default | Description |
|---|---|---|---|
| `flow` | `Steps<Data, Id, S>` | | From `useSteps`. The shell keeps no state of its own. |
| `steps` | `{ [K in Id]: ReactNode }` | | Each step's content, keyed by the flow's ids: a typo or a missing step fails to compile. Only the open step renders, and it keeps rendering while it leaves. |
| `preview` | `ReactNode` | | Beside the steps from 48rem of container width. |
| `compactPreview` | `ReactNode` | | Above the heading below 48rem, until the flow completes. |
| `done` | `ReactNode` | | Replaces the steps once complete. Give it a heading: focus moves there. |
| `labels` | `Partial<OnboardingLabels>` | Continue, Finish, Back, Skip, "2 of 4" | A step's `action` overrides the primary label. |
| `manageFocus` | `boolean \| (() => boolean)` | `true` | Move focus to each new step's heading. For a scripted demo pass `useAutoplay`'s `byVisitor`: asked once per step change, it moves focus only when the visitor caused the change, never for the script. When focus stays, the live region announces the step instead. |
| `className` | `string` | `""` | The root is a size container and a flex column; give it a height. |

The step engine (`useSteps`, `StepDef`, `webStorage`, `useAutoplay`) is documented in the Onboarding core primitive, installed with the shell.

## Keyboard

| Key | Action |
|---|---|
| `Enter` | Continues when the step is valid, from a field, a radio, the heading or anywhere in the form that is not a button or link. |
| `Escape` | Goes back, unless a menu, listbox or dialog inside the step has it. |
| `Tab` | Moves through the step, then Back, Skip and the primary action. |

## Accessibility

- The bar is a `progressbar` valued in steps, with "Step 2 of 4: Pick your URL" as its text.
- Each step is announced once. When the person moves, focus goes to the new step's heading (never on first load), which reads the question. When focus does not move (`manageFocus` off, or a script's move), a polite live region that stays mounted says "Step 2 of 4: Pick your URL" instead.
- Back keeps its place on the first step, invisible, so the primary action never moves. A disabled Continue stays focusable.
- An `onNext` or `onComplete` failure is shown above the footer in a `role="alert"` line.

## Motion

Steps change in sequence, never cross-faded: the outgoing step leaves 4px against the direction of travel in 120ms on the exit curve, then the next arrives from 8px along it over 200ms on the enter curve, so forward moves left and Back moves right. Each segment of the bar fills from the left on `smooth` and empties when you go back past it (transform only). The primary label swaps with a blur while its width springs ("Continue" → "Create workspace"). Reduced motion: steps cross in 100ms of opacity and the bar jumps.

## Notes for AI

Use it for any first-run flow with one question per screen. Create the flow with `useSteps`, put validation in each step's `valid`, async work in `onNext` or `onComplete`, and branch with `when`; the shell reads all of it. Use `preview` for a live result beside the steps.

Don't add your own progress bar, step counter, Back or Continue buttons, step headings or focus handling: the shell draws them from the step definitions. Don't render steps conditionally yourself; pass each in `steps` so the exit animation keeps the outgoing content.

## Related

- [Workspace Setup](/components/workspace-setup): a full flow on this shell, with a live preview.
- [Onboarding Checklist](/components/onboarding-checklist): setup tasks inside the product.
