← Back to UI screens

Live: try it with the mouse and the keyboard (Tab, arrows, Escape).

ScreenshotStepper component — web, corporate style
Theme

Stepper component — web, corporate style

Stepper UI component for websites and web apps, shown in Mintwell, a fictional finance product. Shows a five-step progress stepper with completed, current and upcoming steps, Back and Next buttons, and every step state (default, hover, focus-visible, current, disabled). Corporate style: IBM Plex Sans, a dark brand-coloured sidebar, 4px corners and subtle shadows. Comes with copyable HTML, CSS and a few lines of plain JavaScript (keyboard support and ARIA per the WAI-ARIA Authoring Practices), a live demo, and a DESIGN.md with light and dark colour tokens.

Open live HTML ↗ Download DESIGN.md
Pattern
Stepper
Device
Web
Style
Corporate
Product (fictional)
Mintwell
Category
Finance

Spec

Colours (light)
background #f8f9fc · surface #eaeef5 · text #1f232b · muted #5e636f · primary #0087b2 · accent #6681c7
Colours (dark)
background #0e1015 · surface #1a1d24 · text #f2f3f5 · muted #adb1bb · primary #47c9ff · accent #93b0fa
Fonts
IBM Plex Sans (headings and body), JetBrains Mono (code) · Google Fonts, under their own licences
Light and dark
Both included; follows the system setting
ARIA pattern
Semantic HTML, no widget role needed
Keyboard
Back and Next buttons; the step is announced
States
default, hover, focus-visible, selected, disabled
stepperwebfinancebuttonprogress

Code

Paste the HTML where the component goes, the CSS into your stylesheet and the script before </body>. The tokens block holds the light and dark colours; swap in your own.

HTML
<div class="mgd-stepper" data-step="3">
  <ol class="mgd-steps" aria-label="Open a business account">
    <li class="mgd-steps__item is-done"><span class="mgd-steps__dot" aria-hidden="true"><svg class="i" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></span><span><span class="mgd-sr">Completed: </span>Business details</span></li>
    <li class="mgd-steps__item is-done"><span class="mgd-steps__dot" aria-hidden="true"><svg class="i" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></span><span><span class="mgd-sr">Completed: </span>Owners</span></li>
    <li class="mgd-steps__item" aria-current="step"><span class="mgd-steps__dot" aria-hidden="true">3</span><span><span class="mgd-sr">Current: </span>Verify identity</span></li>
    <li class="mgd-steps__item"><span class="mgd-steps__dot" aria-hidden="true">4</span><span><span class="mgd-sr">Not started: </span>Fund account</span></li>
    <li class="mgd-steps__item"><span class="mgd-steps__dot" aria-hidden="true">5</span><span><span class="mgd-sr">Not started: </span>Review</span></li>
  </ol>
  <div class="mgd-steps__panel">
    <h3 class="mgd-steps__title">Step 3 of 5: Verify identity</h3>
    <p class="mgd-steps__text">A photo of a passport or driving licence and a quick selfie.</p>
    <div class="mgd-steps__nav"><button class="mgd-btn" type="button" data-step-go="-1">Back</button><button class="mgd-btn mgd-btn--primary" type="button" data-step-go="1">Next</button></div>
  </div>
  <p class="mgd-sr" aria-live="polite" data-step-live></p>
</div>
CSS
/* Tokens (light + dark) */
:root{--bg:#f8f9fc;--surface:#eaeef5;--border:#c9cdd7;--text:#1f232b;--muted:#5e636f;--primary:#0087b2;--on-primary:#111111;--accent:#6681c7;--font-h:"IBM Plex Sans",Georgia,serif;--font-b:"IBM Plex Sans",system-ui,sans-serif;--hw:600;
--c-bw:1px;--c-line:var(--border);--c-r:4px;--c-pill:4px;--c-sh:0 1px 2px rgb(0 0 0 / .08);--c-pop:0 8px 24px rgb(0 0 0 / .16);--c-tt:none;--c-ls:0;--c-fw:600;--c-fs:14px;--c-ring:2px solid var(--primary);--c-ind:3px;color-scheme:light}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0e1015;--surface:#1a1d24;--border:#30333b;--text:#f2f3f5;--muted:#adb1bb;--primary:#47c9ff;--on-primary:#111111;--accent:#93b0fa;color-scheme:dark}
}
:root[data-theme="dark"]{--bg:#0e1015;--surface:#1a1d24;--border:#30333b;--text:#f2f3f5;--muted:#adb1bb;--primary:#47c9ff;--on-primary:#111111;--accent:#93b0fa;color-scheme:dark}

/* Component */
.mgd-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.mgd-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:40px;padding:0 16px;border:var(--c-bw) solid var(--c-line);border-radius:var(--c-pill);background:var(--surface);color:var(--text);font:var(--c-fw) var(--c-fs)/1 var(--font-b);text-transform:var(--c-tt);letter-spacing:var(--c-ls);box-shadow:var(--c-sh);cursor:pointer;white-space:nowrap;transition:background-color .12s,border-color .12s}
.mgd-btn:hover,.mgd-btn.is-hover{background:color-mix(in oklab,var(--primary) 9%,var(--surface))}
.mgd-btn:focus-visible,.mgd-btn.is-focus{outline:var(--c-ring);outline-offset:2px}
.mgd-btn[aria-expanded="true"],.mgd-btn.is-open{background:color-mix(in oklab,var(--primary) 15%,var(--surface))}
.mgd-btn--primary{background:var(--primary);border-color:var(--primary);color:var(--on-primary)}
.mgd-btn--primary:hover,.mgd-btn--primary.is-hover{background:color-mix(in oklab,var(--primary) 84%,var(--text))}
.mgd-btn:disabled,.mgd-btn[aria-disabled="true"],.mgd-btn.is-disabled{opacity:.5;cursor:not-allowed;box-shadow:none}
.mgd-btn svg{flex:none}
.mgd-steps{list-style:none;margin:0 0 22px;padding:0;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px;counter-reset:step}
.mgd-steps__item{position:relative;display:grid;justify-items:center;gap:8px;text-align:center;font:500 13px/1.3 var(--font-b);color:var(--muted);text-transform:var(--c-tt);letter-spacing:var(--c-ls)}
.mgd-steps>.mgd-steps__item:not(:first-child)::before{content:"";position:absolute;top:17px;right:calc(50% + 24px);width:calc(100% - 40px);height:2px;background:var(--border)}
.mgd-steps>.mgd-steps__item.is-done:not(:first-child)::before,.mgd-steps>.mgd-steps__item[aria-current="step"]:not(:first-child)::before{background:var(--primary)}
.mgd-steps__dot{width:36px;height:36px;display:grid;place-items:center;border-radius:50%;border:2px solid var(--muted);background:var(--bg);color:var(--text);font:600 14px/1 var(--font-b);font-variant-numeric:tabular-nums;transition:background-color .14s,border-color .14s}
.mgd-steps__item.is-done .mgd-steps__dot{background:var(--primary);border-color:var(--primary);color:var(--on-primary)}
.mgd-steps__item[aria-current="step"],.mgd-steps__item.is-on{color:var(--text);font-weight:700}
.mgd-steps__item[aria-current="step"] .mgd-steps__dot,.mgd-steps__item.is-on .mgd-steps__dot{border-color:var(--primary);box-shadow:0 0 0 4px color-mix(in oklab,var(--primary) 22%,transparent)}
.mgd-steps__item.is-hover .mgd-steps__dot{background:color-mix(in oklab,var(--primary) 10%,var(--bg))}
.mgd-steps__item.is-focus .mgd-steps__dot{outline:var(--c-ring);outline-offset:2px}
.mgd-steps__item.is-off{opacity:.45}
.mgd-steps__panel{max-width:560px;padding:18px;background:var(--surface);border:var(--c-bw) solid var(--c-line);border-radius:var(--c-r);box-shadow:var(--c-sh)}
.mgd-steps__panel h3{margin:0 0 4px;font:var(--hw) 19px/1.25 var(--font-h)}
.mgd-steps__panel p{margin:0;color:var(--muted)}
.mgd-steps__nav{display:flex;gap:10px;margin-top:16px}
@media (max-width:640px){.mgd-steps{grid-template-columns:1fr;gap:0}
.mgd-steps__item{grid-template-columns:36px 1fr;justify-items:start;align-items:center;text-align:left;padding:0 0 18px}
.mgd-steps>.mgd-steps__item:not(:first-child)::before{top:auto;bottom:calc(100% - 0px);right:auto;left:17px;width:2px;height:18px}}
@media (prefers-reduced-motion:reduce){.mgd-steps,.mgd-steps *{transition:none!important;animation-duration:2.4s!important}}
JavaScript
document.querySelectorAll(".mgd-stepper").forEach(function (box) {
  var items = [].slice.call(box.querySelectorAll(".mgd-steps__item")), n = items.length, live = box.querySelector("[data-step-live]");
  var texts = ["Legal name, address and what the business does.","Everyone who owns 25% or more.","A photo of a passport or driving licence and a quick selfie.","Move at least $1 in from another bank.","Check everything once, then open the account."];
  var check = items[0].querySelector(".mgd-steps__dot").innerHTML;
  function render(step, announce) {
    box.dataset.step = step;
    items.forEach(function (li, i) {
      var done = i < step - 1, cur = i === step - 1;
      li.classList.toggle("is-done", done);
      if (cur) li.setAttribute("aria-current", "step"); else li.removeAttribute("aria-current");
      li.querySelector(".mgd-steps__dot").innerHTML = done ? check : String(i + 1);
      li.querySelector(".mgd-sr").textContent = done ? "Completed: " : cur ? "Current: " : "Not started: ";
    });
    var title = "Step " + step + " of " + n + ": " + items[step - 1].lastElementChild.lastChild.textContent;
    box.querySelector(".mgd-steps__title").textContent = title;
    box.querySelector(".mgd-steps__text").textContent = texts[step - 1];
    var back = box.querySelector('[data-step-go="-1"]'), next = box.querySelector('[data-step-go="1"]');
    back.setAttribute("aria-disabled", step === 1); next.textContent = step === n ? "Open account" : "Next";
    if (announce) live.textContent = title;
  }
  box.addEventListener("click", function (e) {
    var b = e.target.closest("[data-step-go]"); if (!b || b.getAttribute("aria-disabled") === "true") return;
    var s = Math.min(n, Math.max(1, +box.dataset.step + +b.dataset.stepGo)); if (s !== +box.dataset.step) render(s, true);
  });
  render(+box.dataset.step, false);
});

DESIGN.md

Component section (states, ARIA, keyboard)
## Component: Stepper

Progress through a short multi-step task, with completed, current and upcoming steps and Back / Next buttons.

- **Pattern:** Semantic HTML (no ARIA widget role needed)
- **Roles and attributes:** An ordered list of steps; the current step has aria-current="step"; completed and upcoming states are spelled out in visually hidden text; a polite live region announces the step on Back / Next.
- **States drawn:** default, hover, focus-visible, selected, disabled
- **Focus:** a visible `:focus-visible` outline (2px solid primary), never removed
- **Style knobs (corporate):** border 1px, corners 4px, shadow `0 1px 2px rgb(0 0 0 / .08)`, labels sentence case
- **Motion:** transitions are short (≤160ms) and switched off under `prefers-reduced-motion: reduce`
- **Code:** HTML + CSS + a few lines of plain JavaScript (no libraries); colours come only from the tokens above, so light and dark both work

### Keyboard
- **Tab:** reach Back and Next
- **Enter / Space:** go back or forward one step

### Licence
Original Made Good Designs code: free to use and adapt in any project, including commercial ones, with no credit needed. Fonts are Google Fonts under their own licences.
AI

Build this with your AI

Fonts: IBM Plex Sans (Google Fonts)

Writing about this component?

More stepper components

Stepper component — web, minimal (Mintwell)
web · minimal
Stepper component — web, editorial (Mintwell)
web · editorial
Stepper component — web, brutalist (Mintwell)
web · brutalist
Stepper component — web, luxury (Mintwell)
web · luxury
Stepper component — web, playful (Mintwell)
web · playful
Stepper component — web, retro (Mintwell)
web · retro
Stepper component — web, monochrome (Mintwell)
web · monochrome
Stepper component — mobile, corporate (Mintwell)
mobile · corporate
Building with an AI coding agent? Every screen here is also in the free Made Good Designs MCP server: ask Claude, Cursor or ChatGPT to search_ui_screens (e.g. “billing settings, editorial style”), get_ui_flow for a whole journey, and get_ui_screen_code to pull the HTML and DESIGN.md straight into your project.

Save to board