Skip to main content

Overview

The Stepper component guides users through a multi-step process. It displays step indicators, manages active step state, and supports both horizontal and vertical layouts.

Installation

import { Stepper, Step } from '@svelte-atoms/core';

Basic Usage

<script>
  import { Stepper, Step } from '@svelte-atoms/core';
  import { Button } from '@svelte-atoms/core';
  
  let currentStep = $state(0);
  
  const steps = [
    { header: 'Account', body: 'Enter your details' },
    { header: 'Address', body: 'Shipping information' },
    { header: 'Payment', body: 'Payment method' }
  ];
</script>

<Stepper.Root bind:step={currentStep}>
  {#snippet children({ stepper })}
    <Stepper.Header class="flex justify-between">
      {#each steps as stepData, i}
        <Step.Root index={i}>
          {#snippet children({ step })}
            <Step.Header class="flex flex-col items-center">
              <Step.Indicator />
              <Step.Title>{stepData.header}</Step.Title>
            </Step.Header>
            
            <Step.Body>
              <h3>{stepData.header}</h3>
              <p>{stepData.body}</p>
            </Step.Body>
          {/snippet}
        </Step.Root>
      {/each}
    </Stepper.Header>
    
    <Stepper.Body>
      <Stepper.Content />
    </Stepper.Body>
    
    <Stepper.Footer class="flex justify-between">
      <Button 
        disabled={stepper.state.isFirstStep}
        onclick={() => stepper.state.navigation.previous()}
      >
        Previous
      </Button>
      <Button 
        onclick={() => stepper.state.navigation.next()}
      >
        {stepper.state.isLastStep ? 'Complete' : 'Next'}
      </Button>
    </Stepper.Footer>
  {/snippet}
</Stepper.Root>

Stepper.Root Props

step
number
default:"0"
The active step index (0-based)
linear
boolean
default:"false"
Enforce linear progression (users can only navigate to adjacent steps)
disabled
boolean
default:"false"
Disable all stepper interactions
factory
Factory<StepperBond>
Custom factory function for creating stepper bond
preset
string
default:"stepper"
Preset key for styling

Step.Root Props

index
number
required
The step index (0-based)
disabled
boolean
default:"false"
Disable this specific step
completed
boolean
default:"false"
Mark this step as completed
optional
boolean
default:"false"
Mark this step as optional
factory
Factory<StepBond>
Custom factory function for creating step bond

Step.Indicator Props

Extends all HtmlAtomProps for div elements. Visual indicator showing step number or status.

Step.Header Props

Extends all HtmlAtomProps for div elements. Clickable header for the step.

Step.Title Props

Extends all HtmlAtomProps for div elements. Title text for the step.

Step.Description Props

Extends all HtmlAtomProps for p elements. Optional description text for the step.

Step.Body Props

Extends all HtmlAtomProps for div elements. Content that displays when step is active.

Step.Separator Props

Extends all HtmlAtomProps for div elements. Visual separator between steps.

Stepper.Header Props

Extends all HtmlAtomProps for div elements. Container for step indicators.

Stepper.Body Props

Extends all HtmlAtomProps for div elements. Container for active step content.

Stepper.Content Props

Extends all HtmlAtomProps for div elements. Renders the active step’s body content. Extends all HtmlAtomProps for div elements. Container for navigation controls.

Stepper State API

The stepper bond provides navigation methods and state:
stepper.state.navigation.next()      // Go to next step
stepper.state.navigation.previous()  // Go to previous step
stepper.state.navigation.reset()     // Reset to first step
stepper.state.navigation.goTo(index) // Go to specific step

stepper.state.isFirstStep  // Boolean: is on first step
stepper.state.isLastStep   // Boolean: is on last step

Subcomponents

Stepper.Root

Root container managing stepper state.

Stepper.Header

Container for step indicators/headers.

Stepper.Body

Container for active step content.

Stepper.Content

Renders the active step’s body (teleported from Step.Body). Container for navigation buttons.

Step.Root

Container for an individual step.

Step.Header

Clickable header/trigger for the step.

Step.Indicator

Visual indicator (number, icon, checkmark).

Step.Title

Step title text.

Step.Description

Optional step description.

Step.Body

Step content that displays when active.

Step.Separator

Line connecting step indicators.

Examples

Horizontal Stepper with Linear Navigation

<script>
  import { Stepper, Step } from '@svelte-atoms/core';
  import { Button } from '@svelte-atoms/core';
  
  let activeStep = $state(0);
  
  const steps = [
    { header: 'Account Information', body: 'Enter your details' },
    { header: 'Address', body: 'Shipping address' },
    { header: 'Payment', body: 'Payment method' },
    { header: 'Review', body: 'Review and confirm', optional: true }
  ];
</script>

<Stepper.Root bind:step={activeStep} linear>
  {#snippet children({ stepper })}
    <Stepper.Header class="flex justify-between">
      {#each steps as stepData, i}
        <Step.Root index={i} optional={stepData.optional}>
          {#snippet children({ step })}
            <Step.Header class="flex flex-col gap-2 flex-1">
              <div class="flex items-center w-full">
                <Step.Indicator />
                <Step.Separator />
              </div>
              
              <div class="flex flex-col">
                <Step.Title>
                  {stepData.header}
                  {#if stepData.optional}
                    <span class="text-xs">(Optional)</span>
                  {/if}
                </Step.Title>
                <Step.Description class="text-xs text-muted-foreground">
                  {stepData.body}
                </Step.Description>
              </div>
            </Step.Header>
            
            <Step.Body>
              <h3 class="text-xl font-semibold mb-4">
                Step {i + 1}: {stepData.header}
              </h3>
              <p>{stepData.body}</p>
            </Step.Body>
          {/snippet}
        </Step.Root>
      {/each}
    </Stepper.Header>
    
    <Stepper.Body class="my-6">
      <Stepper.Content class="min-h-50 p-6" />
    </Stepper.Body>
    
    <Stepper.Footer class="flex justify-between">
      <Button
        variant="outline"
        disabled={stepper.state.isFirstStep}
        onclick={() => stepper.state.navigation.previous()}
      >
        Previous
      </Button>
      
      <div class="flex gap-2">
        <Button variant="ghost" onclick={() => stepper.state.navigation.reset()}>
          Reset
        </Button>
        {#if stepper.state.isLastStep}
          <Button onclick={() => alert('Complete!')}>Complete</Button>
        {:else}
          <Button onclick={() => stepper.state.navigation.next()}>Next</Button>
        {/if}
      </div>
    </Stepper.Footer>
  {/snippet}
</Stepper.Root>

Vertical Stepper

<script>
  import { Stepper, Step } from '@svelte-atoms/core';
  
  let step = $state(1);
  
  const steps = [
    { header: 'Step 1', body: 'First step content' },
    { header: 'Step 2', body: 'Second step content' },
    { header: 'Step 3', body: 'Third step content' }
  ];
</script>

<Stepper.Root class="flex gap-4" bind:step>
  {#snippet children({ stepper })}
    <div class="flex gap-4">
      <Stepper.Header>
        {#each steps as stepData, i}
          <Step.Root index={i}>
            {#snippet children({ step })}
              <Step.Header class="flex w-full">
                <div class="flex gap-2">
                  <div class="flex flex-col">
                    <Step.Indicator />
                    <Step.Separator class="w-[2px] min-h-10 translate-x-[15px]" />
                  </div>
                  <div class="flex flex-col">
                    <Step.Title>{stepData.header}</Step.Title>
                    <Step.Description>{stepData.body}</Step.Description>
                  </div>
                </div>
              </Step.Header>
              
              <Step.Body>
                <h3>{stepData.header}</h3>
                <p>{stepData.body}</p>
              </Step.Body>
            {/snippet}
          </Step.Root>
        {/each}
      </Stepper.Header>
      
      <Stepper.Body>
        <Stepper.Content class="min-w-96 p-6" />
      </Stepper.Body>
    </div>
    
    <Stepper.Footer class="flex justify-between">
      <Button onclick={() => stepper.state.navigation.previous()}>
        Previous
      </Button>
      <Button onclick={() => stepper.state.navigation.next()}>
        Next
      </Button>
    </Stepper.Footer>
  {/snippet}
</Stepper.Root>

With Animations

<script>
  import { Stepper, Step } from '@svelte-atoms/core';
  import { animate } from 'motion';
</script>

<Stepper.Root>
  {#snippet children({ stepper })}
    <Stepper.Header>
      {#each steps as stepData, i}
        <Step.Root index={i}>
          {#snippet children({ step })}
            <Step.Header>
              <Step.Indicator />
              <Step.Title>{stepData.header}</Step.Title>
            </Step.Header>
            
            <Step.Body
              enter={(node) => {
                const duration = 0.4;
                animate(node, { opacity: [0, 1], y: [20, 0] }, { duration });
                return { duration: duration * 1000 };
              }}
              exit={(node) => {
                const duration = 0.2;
                animate(node, { opacity: [1, 0] }, { duration });
                return { duration: duration * 1000 };
              }}
            >
              <h3>{stepData.header}</h3>
              <p>{stepData.body}</p>
            </Step.Body>
          {/snippet}
        </Step.Root>
      {/each}
    </Stepper.Header>
    
    <Stepper.Body>
      <Stepper.Content />
    </Stepper.Body>
  {/snippet}
</Stepper.Root>

With Completed Steps

<script>
  let step = $state(1);
  
  const steps = $state([
    { header: 'Account', completed: true },
    { header: 'Address', completed: false },
    { header: 'Payment', completed: false }
  ]);
  
  function markComplete(index: number) {
    steps[index].completed = true;
  }
</script>

<Stepper.Root bind:step>
  {#snippet children({ stepper })}
    <Stepper.Header>
      {#each steps as stepData, i}
        <Step.Root index={i} completed={stepData.completed}>
          {#snippet children({ step })}
            <Step.Header>
              <Step.Indicator />
              <Step.Title>{stepData.header}</Step.Title>
            </Step.Header>
            
            <Step.Body>
              <h3>{stepData.header}</h3>
              <Button onclick={() => markComplete(i)}>Mark Complete</Button>
            </Step.Body>
          {/snippet}
        </Step.Root>
      {/each}
    </Stepper.Header>
    
    <Stepper.Body>
      <Stepper.Content />
    </Stepper.Body>
  {/snippet}
</Stepper.Root>

Accessibility

  • Keyboard navigation between steps
  • ARIA attributes for screen readers
  • Visual indicators for active, completed, and disabled states
  • Focus management
  • Linear mode prevents jumping to incomplete steps

Build docs developers (and LLMs) love