Stepper
<ol aria-label="Progress">
Guides users through a linear multi-step process. Shows completed, active, and upcoming steps with a connecting line.
Overview
Preview
Account
Profile
Plan
Review
Profile
Tell us a bit about yourself. This appears on your public profile.
Horizontal stepper
Preview
html
<nav aria-label="Progress">
<ol class="flex items-center">
<!-- Completed step -->
<li class="flex flex-1 items-center">
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#D40C37] text-white text-xs font-semibold">
<svg class="h-4 w-4" ...>✓</svg>
</div>
<div class="mx-2 flex-1 h-0.5 rounded-full bg-[#D40C37]"></div>
</li>
<!-- Active step -->
<li class="flex flex-1 items-center">
<div
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#D40C37] text-white
text-xs font-semibold ring-4 ring-[#D40C37]/20"
aria-current="step"
>2</div>
<div class="mx-2 flex-1 h-0.5 rounded-full bg-zinc-800"></div>
</li>
<!-- Upcoming step -->
<li class="flex items-center">
<div class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 border-zinc-700 text-zinc-500 text-xs font-semibold">
3
</div>
</li>
</ol>
</nav>With labels
html
<!-- Labels sit below each step circle -->
<div class="mt-2 flex">
<div class="flex-1 text-center">
<p class="text-[10px] font-semibold uppercase tracking-widest text-zinc-500">Account</p>
</div>
<div class="flex-1 text-center">
<p class="text-[10px] font-semibold uppercase tracking-widest text-zinc-200">Profile</p>
</div>
<div class="flex-1 text-center">
<p class="text-[10px] font-semibold uppercase tracking-widest text-zinc-700">Plan</p>
</div>
</div>Full Vue implementation
vue
<script setup>
import { ref } from 'vue'
const current = ref(0)
const steps = [
{ label: 'Account', desc: 'Email and password.' },
{ label: 'Profile', desc: 'Your public profile details.' },
{ label: 'Plan', desc: 'Choose a subscription plan.' },
{ label: 'Review', desc: 'Confirm and submit.' },
]
</script>
<template>
<!-- Step indicators -->
<nav aria-label="Progress">
<ol class="flex items-center">
<li v-for="(step, i) in steps" :key="step.label" class="flex items-center" :class="{ 'flex-1': i < steps.length - 1 }">
<button
@click="current = i"
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-xs font-semibold transition-all"
:class="
i < current ? 'bg-[#D40C37] text-white' :
i === current ? 'bg-[#D40C37] text-white ring-4 ring-[#D40C37]/20' :
'border-2 border-zinc-700 text-zinc-500 hover:border-zinc-500'
"
:aria-current="i === current ? 'step' : undefined"
>
<svg v-if="i < current" class="h-4 w-4" ...>✓</svg>
<span v-else>{{ i + 1 }}</span>
</button>
<div v-if="i < steps.length - 1"
class="mx-2 flex-1 h-0.5 rounded-full transition-colors duration-300"
:class="i < current ? 'bg-[#D40C37]' : 'bg-zinc-800'"
></div>
</li>
</ol>
</nav>
<!-- Step content -->
<div class="mt-6 rounded-xl border border-zinc-800 bg-zinc-900/50 p-5">
<p class="text-sm font-semibold text-zinc-200">{{ steps[current].label }}</p>
<p class="mt-1 text-xs text-zinc-500">{{ steps[current].desc }}</p>
</div>
<!-- Navigation -->
<div class="mt-4 flex justify-between">
<button
@click="current--"
:disabled="current === 0"
class="rounded-md border border-zinc-700 px-4 py-2 text-sm font-medium text-zinc-300
hover:bg-zinc-800 disabled:opacity-30 disabled:pointer-events-none transition-colors"
>
Back
</button>
<button
@click="current < steps.length - 1 ? current++ : submit()"
class="rounded-md bg-[#D40C37] px-4 py-2 text-sm font-medium text-white
hover:bg-[#b50a2f] transition-colors"
>
{{ current === steps.length - 1 ? 'Submit' : 'Continue' }}
</button>
</div>
</template>Accessibility notes
- Use
<ol>(ordered list) — step order is semantically meaningful - Mark the active step with
aria-current="step" - Wrap in
<nav aria-label="Progress">to create a navigation landmark - If steps are clickable (go back), ensure they receive keyboard focus and have meaningful labels
- Completed steps should visually distinguish from upcoming steps — don't rely on colour alone (use the checkmark icon)