AI Features

Multi-Step and Wizard Forms

Learn how to structure multi-step forms as coordinated rendering systems, where shared state persists, progress is explicit, cross-step validation gates commits, and step transitions are scheduled without tearing down the current UI.

Multi-step forms often become the highest-risk UI in a product, not because the UI is complex, but because the interaction is long-lived. A user might spend minutes entering data across multiple steps. The expectation is that what was typed stays safe, that the “Back” button does not punish progress, and that the form feels calm even when the app is slow. When those expectations are broken, trust drops quickly because the cost of failure is high: re-entering information, losing progress, or guessing what happened during a transition.

A typical implementation starts innocently. Step 1 is a component with local state. Step 2 is another component with local state. Conditional rendering swaps one for the other. When Continue is clicked, a step value is updated and the UI switches. Validation is added to the submit handler. Progress is shown with a simple “Step 1 of 3” label. For small flows, this works.

The problems begin when production requirements arrive. Async validation or preparing the next step is taking longer. Partial saving is added. Optional steps are introduced. A review step needs all prior data. Back navigation must preserve earlier inputs. Computed defaults depend on earlier fields. Each new requirement increases the likelihood that a step change will cause the UI to tear, flash, or reset.

This is where users feel the cracks. A slow Step 2 can cause Step 1 to disappear immediately, replaced by a fallback, even though Step 1 could have stayed usable. Going back can unmount Step 2 and wipe the step-local state, which loses progress. Validation errors can appear only after a transition, forcing manual navigation backward. Focus can jump unpredictably because the DOM tree was replaced. Developers often respond with defensive workarounds: repeatedly lifting state, memoizing entire steps, preventing unmounts, rendering hidden components, or turning steps into routes to keep state stable.

The deeper issue is architectural. A wizard is treated as a sequence of pages that replace each other. That mental model produces instability because replacement implies unmounting, and unmounting destroys step-owned state and breaks continuity. A wizard is not experienced in page navigation. It is experienced as one continuous interaction with checkpoints.

React 19 provides a better way to model this. Instead of “switching screens,” a wizard can be treated as a coordination problem: which UI is currently committed, which work is preparing next, what data must persist regardless of which step is visible, and what validation rules must be satisfied before a new step is allowed to commit. When wizards align with React’s render and commit model, mounting behavior stops being something to fight and becomes something to control.

A wizard is a sequence of coordinated commits, not a sequence of pages
A wizard is a sequence of coordinated commits, not a sequence of pages

The above diagram shows a single, persistent form shell that owns all form states. Inside it, Step A is currently committed and visible. When the user advances, Step B begins rendering in the background while Step A remains interactive. Once Step B is ready, React commits it, replacing Step A in a single, calm transition. At no point does the shell reset or the state disappear.

Wizard steps are commit boundaries, not screens

...