stepperize-svelte
v0.1.0
Published
The type-safe way to build multi-step experiences in Svelte 5. A Svelte runes port of Stepperize by Damián Ricobelli, built on @stepperize/core.
Maintainers
Readme
stepperize-svelte
The type-safe way to build multi-step experiences in Svelte 5.
Define your steps once and get a fully-typed stepper — navigation, validation guards, flow data, completion tracking, and exhaustive matching. Steps are plain objects, custom fields stay typed, and the UI is fully yours.
Credit where it's due: this is a Svelte port of Stepperize by Damián Ricobelli, built directly on his framework-agnostic
@stepperize/coreengine. The API mirrors@stepperize/reactv7 and the behaviour is verified against a port of its test-suite. All credit for the design goes to him. MIT licensed, free for everyone.
Installation
npm install stepperize-svelteRequires Svelte 5 (runes).
Quick start
<script lang="ts">
import { defineStepper } from 'stepperize-svelte';
const checkout = defineStepper([
{ id: 'shipping', title: 'Shipping', description: 'Enter your address' },
{ id: 'payment', title: 'Payment', description: 'Payment details' },
{ id: 'review', title: 'Review', description: 'Confirm your order' }
]);
const stepper = checkout.useStepper();
</script>
<h2>{stepper.current.title}</h2>
<p>{stepper.current.description}</p>
{#if stepper.is('shipping')}
<!-- shipping form -->
{:else if stepper.is('payment')}
<!-- payment form -->
{:else}
<!-- review -->
{/if}
<button onclick={() => stepper.prev()} disabled={!stepper.canPrev}>Back</button>
<button onclick={() => stepper.next()} disabled={!stepper.canNext}>Continue</button>The stepper instance is powered by runes — every property (current, index,
progress, isFirst, isLast, …) is reactive, so it just works inside your
markup, $derived, and $effect.
The stepper instance
| Property / method | Description |
| --- | --- |
| steps, current, id, index, count, progress | Reactive step state |
| isFirst, isLast, canPrev, canNext, isPending | Reactive flags |
| next(payload?), prev(payload?), goTo(id, payload?), reset(payload?) | Async navigation; resolves false when cancelled or at an edge |
| is(id), match({ ...handlers }) | Type-safe checks & exhaustive matching |
| status(id) | Positional status: 'previous' \| 'active' \| 'upcoming' |
| setComplete(id?, value?), isComplete(id?), completed | Explicit completion, separate from position |
| data.get/set/all/clear/reset | Committed cross-step flow data |
| validate(id?) | Validate stored flow data against a step schema |
| canGoTo(id) | Navigation policy check (respects linear) |
Flow data & validation
Attach any Standard Schema (Zod, Valibot,
ArkType, …) to a step to type its flow data and enable validate():
import { z } from 'zod';
const wizard = defineStepper([
{ id: 'name', schema: z.string().min(1) },
{ id: 'done' }
]);
const stepper = wizard.useStepper({
// guard: validate the step being left, including the pending payload
beforeStepChange: async ({ validate }) => (await validate()).success
});
await stepper.next({ data: 'Ada' }); // committed to stepper.data on successControlled usage (URL / router state)
Pass an object with getters so the stepper reads the latest values reactively:
<script lang="ts">
import { page } from '$app/state';
import { goto } from '$app/navigation';
const stepper = wizard.useStepper({
get step() {
return page.params.step;
},
onStepChange: (id) => goto(`/checkout/${id}`),
onInvalidStep: () => goto('/checkout/shipping')
});
</script>parseStep(value) narrows arbitrary strings to known step ids at the boundary.
Sharing a stepper through context
provideStepper is the Svelte equivalent of the React <Provider>:
<!-- Parent.svelte -->
<script lang="ts">
const stepper = wizard.provideStepper();
</script>
<!-- Child.svelte (anywhere in the subtree) -->
<script lang="ts">
const stepper = wizard.useStepper(); // returns the shared instance
</script>shadcn-svelte components
This repo ships a copy-paste shadcn-svelte-style
stepper block in
src/lib/registry/stepper —
StepperRoot, StepperNav, StepperPanel, and StepperControls — plus a full
checkout demo in src/routes/+page.svelte. Copy
the files into your project (they only need Tailwind, clsx, and
tailwind-merge) and style away.
<StepperRoot {stepper}>
<StepperNav />
<StepperPanel>
{#if stepper.is('shipping')}...{/if}
</StepperPanel>
<StepperControls>
<button onclick={() => stepper.prev()} disabled={!stepper.canPrev}>Back</button>
<button onclick={() => stepper.next()} disabled={!stepper.canNext}>Continue</button>
</StepperControls>
</StepperRoot>Definition-level helpers
const wizard = defineStepper(steps, {
defaultStep: 'payment', // initial step
defaultData: {}, // initial flow data
defaultCompleted: [], // initially-completed step ids
linear: false // gate canGoTo to adjacent steps
});
wizard.get('payment'); // typed step lookup
wizard.at(0); // by index
wizard.parseStep(param); // narrow unknown -> step id | undefined
await wizard.validate('name', value); // validate arbitrary valuesCredits
- Damián Ricobelli — author of
Stepperize, the original
React library and the
@stepperize/coreengine this package is built on. - shadcn-svelte — component conventions used by the bundled stepper block.
License
MIT — free for everyone, forever.
