A tiny, fully-themeable, and dependency-free fluid stepper component — for Svelte 5.
Set orientation="vertical" and the
stepper stacks top-to-bottom. Works well as a sidebar rail or alongside vertically-scrolling
content.
Photo by Steve A Johnson on Unsplash
useAutoPlay drives timed step changes
with a visible fill animation. Pause, resume, and loop are all built in.
Photo by National Gallery of Art on Unsplash
Every visual detail is controlled by CSS custom properties — colors, sizes, radii, spacing. Override a few variables to match your branding needs. Pick one to re-skin this page.
Default
Minimal
Hacker
Warm
Brutalist
Default is the built-in look — no CSS or class needed. Pick another theme to see how it's applied.
<Stepper count={5} {active} onStepClick={(i) => (active = i)} />npm i svelte-pasitoOr tell your agent:
Install svelte-pasito (npm i svelte-pasito). Import the Stepper component, then add a stepper to my Svelte file with 5 steps and click-to-navigate.
<script lang="ts"> import { Stepper } from 'svelte-pasito' let active = $state(0)</script><Stepper count={5} {active} onStepClick={(i) => (active = i)}/><script lang="ts"> import { Stepper, useAutoPlay } from 'svelte-pasito' let active = $state(0) const autoplay = useAutoPlay({ count: () => 5, active: () => active, onStepChange: (i) => (active = i), stepDuration: () => 5000, loop: () => true })</script><Stepper count={5} {active} onStepClick={(i) => (active = i)} filling={autoplay.filling} fillDuration={autoplay.fillDuration} onfocusin={autoplay.handleFocusIn} onfocusout={autoplay.handleFocusOut}/>| Prop | Type | Description |
|---|---|---|
count | number | Total number of steps |
active | number | Zero-based active step index |
onStepClick | (index: number) => void | Called when a step is clicked |
orientation | "horizontal" | "vertical" | Layout direction. Default: "horizontal" |
maxVisible | number | Max visible steps before windowing kicks in |
transitionDuration | number | Transition duration in ms. Default: 500 |
easing | string | CSS timing function. Default: cubic-bezier(0.215, 0.61, 0.355, 1) |
filling | boolean | Show fill progress on the active step |
fillDuration | number | Fill animation duration in ms. Default: 3000 |
className | string | Extra class on the container, for variable overrides |
onfocusin | (event: FocusEvent) => void | Focus entered the stepper. Pass autoplay.handleFocusIn |
onfocusout | (event: FocusEvent) => void | Focus left a step. Pass autoplay.handleFocusOut |
| Option | Type | Description |
|---|---|---|
count | () => number | Total number of steps |
active | () => number | Current active step |
onStepChange | (index: number) => void | Called to advance the step |
stepDuration | () => number | Time per step in ms. Default: 3000 |
loop | () => boolean | Loop back to the first step. Default: true |
enabled | () => boolean | Enable/disable autoplay. Default: true |
| Property | Type | Description |
|---|---|---|
playing | boolean | Current playback state (reactive) |
toggle | () => void | Toggle play/pause |
filling | boolean | Pass to Stepper filling prop |
fillDuration | number | Pass to Stepper fillDuration prop |
handleFocusIn | () => void | Pass to Stepper onfocusin: pauses while focused |
handleFocusOut | (event?: FocusEvent) => void | Pass to Stepper onfocusout: resumes if it paused |
Override any of these on the container via className.
| Variable | Default | Description |
|---|---|---|
--pill-dot-size | 8px | Diameter of inactive dots |
--pill-active-width | 24px | Width of the active pill |
--pill-gap | 6px | Space between steps |
--pill-bg | rgba(0,0,0,0.12) | Inactive dot color |
--pill-active-bg | rgba(0,0,0,0.8) | Active pill color |
--pill-fill-bg | rgba(255,255,255,0.45) | Autoplay fill bar color |
--pill-container-bg | rgba(0,0,0,0.04) | Container background |
--pill-container-border | rgba(0,0,0,0.06) | Container border color |
--pill-container-radius | 999px | Container border radius |
.my-dark-stepper { --pill-bg: rgba(255, 255, 255, 0.15); --pill-active-bg: rgba(255, 255, 255, 0.9); --pill-fill-bg: rgba(255, 255, 255, 0.3); --pill-container-bg: rgba(255, 255, 255, 0.1); --pill-container-border: rgba(255, 255, 255, 0.12);}Steps use role="tab" with aria-selected and an aria-label per step. The active
step is keyboard-focusable, and all transitions resolve instantly under prefers-reduced-motion.
Arrow keys work whenever onStepClick is
passed.
| Key | Orientation | Action |
|---|---|---|
← → | Horizontal | Previous / next step (wraps around) |
↑ ↓ | Vertical | Previous / next step (wraps around) |
Home / End | Both | First / last step |
Enter / Space | Both | Select the focused step |
Tab | Both | Move into and out of the stepper (lands on the active step) |
Wire onfocusin and onfocusout to the autoplay handlers (see
Autoplay Usage) and playback pauses while focus is inside the stepper, then resumes when it
leaves — but only if it was playing before. Your play/pause button is a normal button, so Space toggles it.
Added
← → (or ↑ ↓ when vertical) move between steps and wrap around, Home / End jump to the first / last step. Focus follows the selected step.onfocusin and onfocusout props on Stepper.handleFocusIn and handleFocusOut on useAutoPlay / AutoPlayController: wire them to the stepper so autoplay pauses while keyboard focus is inside it and resumes when focus leaves (only if it was playing before).Changed
Changed
Added
Stepper component with horizontal and vertical orientation, windowing via maxVisible, and animated step add/remove.useAutoPlay and AutoPlayController for timed step changes with a fill animation.--pill-* CSS custom properties.