Stepper
Wizard progress — where you are in a checkout, an onboarding, a form too long for one screen.
Walkthrough
1. Basic
bind:step is the index. Steps behind you can be pressed to go back; steps ahead cannot be skipped to.
Step 3 of 4: Payment
Next: Review
<script lang="ts"> import { Stepper, StepperItem } from 'omaris'; let step = $state(2);</script><Stepper bind:step label="Order progress" class="w-full max-w-3xl"> <StepperItem title="Cart" description="3 items" /> <StepperItem title="Shipping" description="Erbil" /> <StepperItem title="Payment" description="Card or cash" /> <StepperItem title="Review" /></Stepper> 2. Checkout
A three-step checkout. The stepper only says where you are; the form under it switches on step. A step that fails validation takes error, and in a column narrower than 36rem the row collapses to "Step 2 of 3 · Payment".
Step 1 of 3: Shipping
Next: Payment
<script lang="ts"> import { Button, DataList, DataListItem, Input, Radio, RadioGroup, Stepper, StepperItem, Text } from 'omaris'; let step = $state(0); let name = $state(''); let city = $state(''); let payment = $state('cash'); let tried = $state(false); const STEPS = 3; const shippingValid = $derived(name.trim() !== '' && city.trim() !== ''); const placed = $derived(step === STEPS); function next() { if (step === 0 && !shippingValid) { tried = true; return; } step += 1; }</script><div class="flex w-full max-w-2xl flex-col gap-6"> <Stepper bind:step label="Checkout"> <StepperItem title="Shipping" description="Where it's going" error={tried && !shippingValid ? 'Name and city needed' : false} /> <StepperItem title="Payment" description="Cash or card" /> <StepperItem title="Review" description="Check and place" /> </Stepper> {#if step === 0} <div class="flex flex-col gap-4"> <Input label="Full name" bind:value={name} invalid={tried && !name.trim()} required /> <Input label="City" bind:value={city} invalid={tried && !city.trim()} required /> </div> {:else if step === 1} <RadioGroup label="Pay with" bind:value={payment}> <Radio value="cash" label="Cash on delivery" /> <Radio value="card" label="Card" description="Visa or Mastercard" /> </RadioGroup> {:else if step === 2} <DataList dividers> <DataListItem label="Name" value={name} /> <DataListItem label="City" value={city} /> <DataListItem label="Payment" value={payment === 'cash' ? 'Cash on delivery' : 'Card'} /> </DataList> {:else} <Text variant="title-md">Order placed — it's on its way to {city}.</Text> {/if} <div class="flex justify-between gap-2"> <Button variant="text" disabled={step === 0 || placed} onclick={() => (step -= 1)}>Back</Button> {#if placed} <Button variant="tonal" onclick={() => ((step = 0), (tried = false))}>Start again</Button> {:else} <Button onclick={next}>{step === STEPS - 1 ? 'Place order' : 'Next'}</Button> {/if} </div></div> 3. Vertical
Vertical, each step's content opens under it. The content stays mounted while it is closed, so a half-filled form survives being stepped away from.
-
-
One is enough to open with.
-
<script lang="ts"> import { Button, Input, Stepper, StepperItem, Text } from 'omaris'; let step = $state(0); let shop = $state('');</script><Stepper orientation="vertical" bind:step label="Open your shop" class="w-96 max-w-full"> <StepperItem title="Name your shop" description="You can change it later"> <div class="flex flex-col items-start gap-3"> <Input label="Shop name" bind:value={shop} class="w-full" /> <Button size="sm" onclick={() => (step = 1)}>Continue</Button> </div> </StepperItem> <StepperItem title="Add a product"> <div class="flex flex-col items-start gap-3"> <Text variant="body-sm" tone="muted">One is enough to open with.</Text> <div class="flex gap-2"> <Button size="sm" variant="text" onclick={() => (step = 0)}>Back</Button> <Button size="sm" onclick={() => (step = 2)}>Continue</Button> </div> </div> </StepperItem> <StepperItem title="Go live" description={shop ? `${shop} opens to everyone` : undefined}> <div class="flex gap-2"> <Button size="sm" variant="text" onclick={() => (step = 1)}>Back</Button> <Button size="sm" onclick={() => (step = 3)}>Publish</Button> </div> </StepperItem></Stepper> 4. Overridden
linear={false} lets any step be picked, with complete marking the ones done out of order. collapse={false} keeps a narrow row a row, counter and upNext reword the collapsed view, and classes reaches every part.
2/3: Team
then Billing
<script lang="ts"> import { Stepper, StepperItem } from 'omaris'; let section = $state(1);</script><div class="flex w-80 max-w-full flex-col gap-8"> <Stepper linear={false} collapse={false} bind:step={section} label="Profile"> <StepperItem title="About" complete classes={{ indicator: 'rounded-shape-sm' }} /> <StepperItem title="Photo" classes={{ indicator: 'rounded-shape-sm' }} /> <StepperItem title="Links" classes={{ indicator: 'rounded-shape-sm' }} /> </Stepper> <Stepper step={1} label="Onboarding" counter={(current, total) => `${current}/${total}`} upNext={(title) => `then ${title}`} classes={{ summary: 'text-primary' }} > <StepperItem title="Account" /> <StepperItem title="Team" /> <StepperItem title="Billing" /> </Stepper></div>