Skip to content
Velocity X Docs
← All components

Stepper / Progress Indicator

Free · MIT

Horizontal multi-step progress indicator with a filling connector line, collapsing to a compact step-count bar on phone.

Category
Core UI
Framework
astro
Version
1.0.0
Peer deps
none

Install

bash
velocityx add stepper

Free and MIT licensed — no account needed. The file lands under src/components/velocity/stepper/ by default (override with --dest), and it's yours to edit from the moment it's written.

Demo

View the live demo on the showcase site ↗

Source

Pulled live from the registry — this is the exact file velocityx add stepper writes into your project right now.

components/velocity/stepper/Stepper.astro

astro
---
//    VELOCITY COMPONENT · theming
// 
//    Colours here are Tailwind `primary-*` / `accent-*`, which resolve through
//    the Velocity preset. Out of the box they are a neutral indigo — NOT the
//    Aidxn brand — so this drops into your project without dragging our look in.
// 
//    ⚡ NOT USING VELOCITY? Run this once, in your project root:
// 
//          velocityx theme
// 
//    It writes velocity-preset.cjs + velocity-theme.css. Add the preset to your
//    tailwind.config, import the css, then set your whole brand in ONE place:
// 
//          :root {
//            --vx-brand: custom;
//            --vx-primary-600: 220 38 38;   <- your action colour
//            --vx-primary-700: 185 28 28;
//          }
// 
//    ⚠️  Raw RGB channels, space separated — NOT hex. `#DC2626` here silently
//        breaks every /opacity variant in the library (bg-primary-600/20 etc).
// 
//    Prefer not to install anything? Find-and-replace `primary-` with your own
//    scale name. Nothing else in this file depends on Velocity.
/**
 * Stepper — horizontal progress indicator for multi-step flows (checkout,
 * onboarding, wizard panels). Connector line fills to the active step.
 * Collapses to a compact "Step N of M" + bar on phone so labels never wrap
 * into the connector.
 *
 * Usage:
 *   <Stepper steps={['Account', 'Business details', 'Payment', 'Review']} current={1} />
 */
export interface Props {
  steps: string[];
  current: number; // 0-indexed
}
const { steps, current } = Astro.props;
const pct = steps.length > 1 ? (current / (steps.length - 1)) * 100 : 100;
---

<div>
  <ol class="hidden sm:flex items-start" aria-label="Progress">
    {steps.map((step, i) => {
      const done = i < current;
      const active = i === current;
      return (
        <li class="relative flex-1 flex flex-col items-center text-center">
          {i > 0 && (
            <span
              aria-hidden="true"
              class:list={['absolute top-4 right-1/2 h-0.5 w-full -z-0', i <= current ? 'bg-primary-600' : 'bg-secondary-200 dark:bg-secondary-800']}
            />
          )}
          <span
            aria-current={active ? 'step' : undefined}
            class:list={[
              'relative z-10 flex h-8 w-8 items-center justify-center rounded-full text-xs font-semibold shrink-0',
              done ? 'bg-primary-600 text-white' : active ? 'bg-white dark:bg-secondary-900 border-2 border-primary-600 text-primary-600' : 'bg-secondary-100 dark:bg-secondary-800 text-secondary-400 dark:text-secondary-500',
            ]}
          >
            {done ? (
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
            ) : (
              i + 1
            )}
          </span>
          <span class:list={['mt-2 text-xs font-medium px-1', active ? 'text-primary-700 dark:text-primary-300' : 'text-secondary-500 dark:text-secondary-400']}>
            {step}
          </span>
        </li>
      );
    })}
  </ol>

  <div class="sm:hidden">
    <div class="flex items-center justify-between text-xs font-medium text-secondary-600 dark:text-secondary-300 mb-1.5">
      <span>Step {current + 1} of {steps.length}</span>
      <span class="text-primary-700 dark:text-primary-300">{steps[current]}</span>
    </div>
    <div class="h-1.5 w-full rounded-full bg-secondary-100 dark:bg-secondary-800" role="progressbar" aria-valuenow={current + 1} aria-valuemin={1} aria-valuemax={steps.length}>
      <div class="h-full rounded-full bg-primary-600 transition-[width] duration-300" style={`width: ${pct}%`} />
    </div>
  </div>
</div>

Theming

Uses the shared primary-* / accent-* scale — see Theming if colours look unstyled after install.