Skip to content

Steps

Steps show where the user is in an ordered, multi-step flow (checkout, onboarding, a wizard). It is a native <ol class="re-steps"> of <li class="re-steps__step">, each carrying data-statuscomplete, current, or upcoming. It works with zero JavaScript: markers auto-number via a CSS counter, a complete step swaps its number for a pure-CSS check, and the connecting rail is drawn entirely in CSS.

It is a display indicator, not an ARIA tablist or navigation widget — there is no roving tabindex and no panel switching. The ordered-list semantics are kept (so assistive tech announces “2 of 4”); the list is stripped visually in CSS rather than with role="list", which would discard that ordinal. The current step carries aria-current="step" on its <li>.

  1. Completed: Account Email and password
  2. Completed: Profile Name and avatar
  3. Billing Payment method
  4. Upcoming: Review Confirm your order
Live example
<ol class="re-steps" data-orientation="vertical">
  <li class="re-steps__step" data-status="complete">
    <a class="re-steps__content" href="#account">
      <span class="re-sr-only">Completed: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Account</span>
        <span class="re-steps__description">Email and password</span>
      </span>
    </a>
  </li>
  <li class="re-steps__step" data-status="complete">
    <a class="re-steps__content" href="#profile">
      <span class="re-sr-only">Completed: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Profile</span>
        <span class="re-steps__description">Name and avatar</span>
      </span>
    </a>
  </li>
  <li class="re-steps__step" data-status="current" aria-current="step">
    <span class="re-steps__content">
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Billing</span>
        <span class="re-steps__description">Payment method</span>
      </span>
    </span>
  </li>
  <li class="re-steps__step" data-status="upcoming">
    <span class="re-steps__content">
      <span class="re-sr-only">Upcoming: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Review</span>
        <span class="re-steps__description">Confirm your order</span>
      </span>
    </span>
  </li>
</ol>

data-orientation="horizontal" flows the steps inline with the rail behind the markers.

  1. Completed: Plan Choose a tier
  2. Build Set it up
  3. Upcoming: Ship Go live
Live example
<ol class="re-steps" data-orientation="horizontal">
  <li class="re-steps__step" data-status="complete">
    <a class="re-steps__content" href="#plan">
      <span class="re-sr-only">Completed: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Plan</span>
        <span class="re-steps__description">Choose a tier</span>
      </span>
    </a>
  </li>
  <li class="re-steps__step" data-status="current" aria-current="step">
    <span class="re-steps__content">
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Build</span>
        <span class="re-steps__description">Set it up</span>
      </span>
    </span>
  </li>
  <li class="re-steps__step" data-status="upcoming">
    <span class="re-steps__content">
      <span class="re-sr-only">Upcoming: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Ship</span>
        <span class="re-steps__description">Go live</span>
      </span>
    </span>
  </li>
</ol>

data-size="sm" and data-size="lg" scale the markers, gap, and type together (mirroring progress); sm drops the description line in horizontal layouts.

  1. Completed: Cart Your items
  2. Shipping Address
  3. Upcoming: Pay Card details
  1. Completed: Cart Your items
  2. Shipping Address
  3. Upcoming: Pay Card details
Live example
<ol class="re-steps" data-orientation="horizontal" data-size="sm">
  <li class="re-steps__step" data-status="complete">
    <span class="re-steps__content">
      <span class="re-sr-only">Completed: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Cart</span>
        <span class="re-steps__description">Your items</span>
      </span>
    </span>
  </li>
  <li class="re-steps__step" data-status="current" aria-current="step">
    <span class="re-steps__content">
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Shipping</span>
        <span class="re-steps__description">Address</span>
      </span>
    </span>
  </li>
  <li class="re-steps__step" data-status="upcoming">
    <span class="re-steps__content">
      <span class="re-sr-only">Upcoming: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Pay</span>
        <span class="re-steps__description">Card details</span>
      </span>
    </span>
  </li>
</ol>

<ol class="re-steps" data-orientation="horizontal" data-size="lg">
  <li class="re-steps__step" data-status="complete">
    <span class="re-steps__content">
      <span class="re-sr-only">Completed: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Cart</span>
        <span class="re-steps__description">Your items</span>
      </span>
    </span>
  </li>
  <li class="re-steps__step" data-status="current" aria-current="step">
    <span class="re-steps__content">
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Shipping</span>
        <span class="re-steps__description">Address</span>
      </span>
    </span>
  </li>
  <li class="re-steps__step" data-status="upcoming">
    <span class="re-steps__content">
      <span class="re-sr-only">Upcoming: </span>
      <span class="re-steps__marker" aria-hidden="true"></span>
      <span class="re-steps__text">
        <span class="re-steps__title">Pay</span>
        <span class="re-steps__description">Card details</span>
      </span>
    </span>
  </li>
</ol>
  • Set each step’s data-status to complete, current, or upcoming, and put aria-current="step" on the same <li> as data-status="current" (exactly once). With no JavaScript, advancing the flow is your framework re-rendering those attributes.
  • A completed step’s .re-steps__content may be a real <a href> or <button> — it becomes focusable and shows a hover/focus affordance. Leave it a <span> for an inert step (upcoming steps should stay inert).
  • The rail tints accent up to the last complete step: a complete step colors the segment leading out of it toward the next step. For a continuous “percent done” bar, compose progress instead — steps is deliberately discrete.
  • Markers carry aria-hidden="true" — the number and check are visual only. Because of that, completion is not programmatically determinable from the marker, so expose it to assistive tech with a visually-hidden status word on each non-current step: <span class="re-sr-only">Completed: </span> (or Upcoming:). The current step is already announced via aria-current. The current step is also distinguished without color (a bolder title, and a system-Highlight marker ring under Windows High Contrast); the completed step by its check glyph.
  • Tune metrics by overriding --re-steps-marker-size, --re-steps-gap, --re-steps-rail-color, etc. on the root or any subtree.
  • Keyboard — there is no widget keyboard model: steps is a display indicator, not a tablist, so there’s no roving tabindex, arrow navigation, or panel switching. The only focusable parts are completed steps whose .re-steps__content you make a real <a href> or <button> — reached with Tab and activated with Enter (or Space for a <button>) like any native link/button. Upcoming and current steps stay inert <span>s.
  • Focus — interactive steps show the standard :focus-visible ring. Vertical layouts use the global outer ring; horizontal layouts (a tight, scrollable row) swap to an inset ring so it never clips at a container edge.
  • Semantics — the native <ol>/<li> ordering is preserved (the list is stripped in CSS, not with role="list", which would discard the “2 of 4” ordinal). The current step carries aria-current="step" on its <li>. Markers are aria-hidden="true" — the number and check are visual only — so completion is exposed to assistive tech with a visually-hidden status word (<span class="re-sr-only">Completed: </span> / Upcoming:) on each non-current step.
  • Notes — status is conveyed without relying on color: the current step has a bolder title plus aria-current, and a complete step is marked by its check glyph. Under Windows High Contrast (forced-colors) the current marker takes a system Highlight ring and the focus ring is re-established as a real outline. Marker and rail transitions are dropped under prefers-reduced-motion. See the accessibility guide for system-wide conventions.