Stepper / Progress Indicator
Free · MITHorizontal 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
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
---
// 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.