Stepper
Overview
A multi-step progress indicator: connectors fill with progress, finished steps turn into checks and the current step has a breathing ring. Comes with Back / Next.
Plain HTML, CSS and a little vanilla JS, with no framework dependency. Copy the prompt into Claude Code, Cursor, Trae or Codex and the model adds Stepper to your project in your colors.
Where to use it
Site headers, tab switches, segmented controls, side navigation and footers.
Install
With Builder, install it with the 8pt CLI or call it directly from an editor connected to 8pt MCP.
$ npm i -g @8pt-dev/cli && 8pt add stepper
mcp › Use 8pt MCP to call get_component("stepper")
Code
Source for Stepper. Motion uses only transform and opacity and respects the Reduce motion setting.
<div class="x-step">
<ol class="x-step__list">
<li class="on"><i>1</i><span>Account</span></li>
<li><i>2</i><span>Team</span></li>
<li><i>3</i><span>Themes</span></li>
<li><i>4</i><span>Done</span></li>
</ol>
<div class="x-step__track" aria-hidden="true"><span></span></div>
<div class="x-step__ctl"><button type="button" data-d="-1" disabled>Back</button><button type="button" data-d="1" class="pri">Next</button></div>
</div>.x-step{position:relative;width:min(340px,100%);color:var(--d-fg,#fff)}
.x-step__list{position:relative;z-index:1;display:grid;grid-template-columns:repeat(4,1fr);margin:0;padding:0;list-style:none}
.x-step__list li{display:grid;justify-items:center;gap:8px;font:500 12px/1 var(--d-font,system-ui,sans-serif);color:var(--d-muted,#9b9ba8);transition:color .3s}
.x-step__list i{position:relative;display:grid;place-items:center;width:30px;height:30px;border-radius:50%;font:700 12px/1 var(--d-font,system-ui,sans-serif);font-style:normal;background:var(--d-card,#14141c);border:1.5px solid var(--d-line,rgba(255,255,255,.16));transition:background-color .3s,border-color .3s,color .3s}
.x-step__list li.on,.x-step__list li.done{color:var(--d-fg,#fff)}
.x-step__list li.on i{border-color:var(--d-accent,#4D74FF);color:var(--d-accent,#4D74FF)}
.x-step__list li.on i::after{content:"";position:absolute;inset:-6px;border-radius:50%;border:1.5px solid var(--d-accent,#4D74FF);opacity:0;animation:x-step-pulse 1.8s ease-out infinite}
@keyframes x-step-pulse{0%{transform:scale(.8);opacity:.7}100%{transform:scale(1.25);opacity:0}}
.x-step__list li.done i{background:var(--d-accent,#4D74FF);border-color:var(--d-accent,#4D74FF);color:#fff;font-size:0}
.x-step__list li.done i::before{content:"✓";font-size:14px}
.x-step__track{position:absolute;left:12.5%;right:12.5%;top:14px;height:2px;border-radius:2px;background:var(--d-line,rgba(255,255,255,.14));overflow:hidden}
.x-step__track span{display:block;height:100%;background:var(--d-accent,#4D74FF);transform-origin:left;transform:scaleX(0);transition:transform .55s cubic-bezier(.65,0,.35,1)}
…const init = (root) => {
const items = [...root.querySelectorAll('.x-step__list li')], bar = root.querySelector('.x-step__track span');
const [prev, next] = root.querySelectorAll('.x-step__ctl button');
let cur = 0;
const draw = () => {
items.forEach((li, i) => { li.classList.toggle('done', i < cur); li.classList.toggle('on', i === cur); });
bar.style.transform = `scaleX(${cur / (items.length - 1)})`;
prev.disabled = cur === 0; next.textContent = cur === items.length - 1 ? 'Start over' : 'Next';
};
prev.addEventListener('click', () => { cur = Math.max(0, cur - 1); draw(); });
next.addEventListener('click', () => { cur = cur === items.length - 1 ? 0 : cur + 1; draw(); });
draw();
…Subscribe to Builder to see the full source.See pricing