Persistence & URL Sync
Keep wizard data across reloads and mirror the active step into the URL.
Persistence & URL Sync
v1.2Long forms lose people when a refresh wipes their answers. persist keeps the
data; syncToUrl keeps the position.
Data goes to sessionStorage["docs-persist"], and the active step is mirrored into ?docs-step=. Reload this page to see both restored.
Resumable wizard
<FormWizard
// Survives a reload. "session" clears with the tab; "local" persists.
persist={{ key: "checkout", storage: "session" }}
// Mirrors the step into ?step=2 so refreshes and shared links land correctly.
syncToUrl
schema={schema}
/>persist
| Option | Type | Default | Description |
|---|---|---|---|
key | string | — | Storage key. Namespace it per form. |
storage | "session" | "local" | "session" | session clears when the tab closes; local survives it. |
Stored data is merged over initialData on mount, so new fields you add later
still get their defaults.
Clear it with ref.current.reset() (which also returns to the start step) or,
in the headless API, wizard.clearPersisted().
const wizardRef = useRef<FormWizardMethods>(null);
<FormWizard
ref={wizardRef}
persist={{ key: "checkout" }}
onComplete={async (data) => {
await submit(data);
wizardRef.current?.reset(); // clears the saved payload too
}}
/>;URL sync
syncToUrl writes the active step into a query parameter, 1-based so the
URL reads naturally (?step=2 is the second step).
<FormWizard syncToUrl /> // ?step=2
<FormWizard syncToUrl={{ param: "stage" }} /> // ?stage=2It uses history.replaceState, so it does not add browser-history entries —
Back leaves the wizard rather than stepping through it. On mount, a step
present in the URL wins over startIndex.
Safety
Both features are best-effort by design:
- Guarded for SSR —
windowis never touched during a server render. - Private browsing, disabled storage and quota errors are swallowed, not thrown. A failed write never breaks the form.
- Malformed stored JSON is ignored and the wizard falls back to
initialData.
sessionStorage and localStorage are readable by any script on the origin
and are not encrypted. Keep card numbers, passwords and tokens out of wizard
data when persistence is on — persist an id and re-fetch instead.
With the headless API
Identical options:
const wizard = useWizard({
stepIds: ["account", "profile", "review"],
persist: { key: "signup", storage: "local" },
syncToUrl: { param: "s" },
});