Skip to main content

Full-Screen Wizard

A full-screen onboarding wizard with a persistent progress bar in the chrome, a mix of component and element-returning loaders, and a final resolve that returns the collected data. Step changes play a Motion crossfade-slidemotion.div wrappers driven by the outlet's transition prop.

The first transition uses a normal component loader. The final transition simulates async work before returning JSX with hydrated props. The progress bar updates immediately on advance — before the async step resolves — because chrome renders outside Suspense.

Click Start Onboarding below to try it.

Result
Loading...
Live Editor

Transition with Motion

The outlet's transition prop receives { previousStep, nextStep, phase, onExited, transitionKey } and returns whatever should render in place of the step slot. The wizard returns two motion.div wrappers — the outgoing step fades out to the left while the incoming step fades in from the right:

The exit wrapper reports completion through onAnimationComplete={onExited}. That single call advances the outlet's phase machine from exitingenteringexited, unmounting the outgoing step when its Motion animation finishes.

Async step loading

The final step waits on a simulated async lookup and then returns JSX with hydrated props:

advance(async () => {
const summary = await loadWizardSummary();
return <StepConfirm summary={summary} />;
});

advance(loader, newContext) accepts a step loader function. That loader can synchronously return a component or asynchronously return a promise. While the loader is pending, the <Suspense> fallback inside WizardChrome shows the loading indicator.

This uses a timeout to mimic a fetch or server-side decision rather than a lazy import, which is often the more practical case for passing props into the next step.

Progress via context

The progress bar reads ctx.step and ctx.total from useSequentContext(). Each advance call passes updated context:

advance(() => StepPreferences, { step: 2, total: 3, data: { name: "…" } })

Because chrome renders outside the Suspense boundary, the progress bar jumps from 33% → 66% immediately when advance is called — before the next step finishes loading.

Key points

  • Any animation library works — Motion is used here as an example; since the slot just owns rendering, you could drive step transitions with Motion, Framer Motion, CSS transitions, or any other animation library you prefer.
  • Chrome outlives async loads — the progress bar and step counter stay mounted while the next step resolves.
  • Context still carries shared flow state — progress metadata stays in context while the final step also receives a hydrated summary prop.
  • No centralized step list — the total step count is just data passed through context; you can change it without touching any config.