Accessibility pattern · Feedback and status

Progress steps

The steps are an ordered list; the current one carries aria-current="step", and every step starts with a hidden word, Completed, Current or Not started, so its state never rests on a color or a tick. Back and Next move the current step, announce "Step 3 of 4: Payment" politely and send focus to the new step's heading, so reading carries on from the right place.

WCAG criteria
6
Keyboard rules
4
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

  1. Completed: Bag
  2. Current: Address
  3. Not started: Payment
  4. Not started: Review

Step 2 of 4

Delivery address

Asha Rao, 14 MG Road, Kothrud, Pune 411038

Arrives Thursday, 9 October. Free delivery.

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
TabSkips the step list, which is information, and moves from the step's content to Back and Next.
Enter or SpaceOn Next, marks this step completed, makes the next one current, announces it and moves focus to its heading.
Enter or SpaceOn Back, makes the previous step current again and moves focus to its heading.
Enter or SpaceOn Place order, the last step's button, marks every step completed; Start again goes back to the first.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
A screen reader reads the step listCheckout progress, list, 4 items. Completed: Bag. Current: Address, current step. Not started: Payment. Not started: Review
Next is pressed on step 2Step 3 of 4: Payment
Focus lands on the new step's headingPayment, heading level 3
Back is pressedStep 2 of 4: Address
The order is placedOrder placed. All 4 steps completed

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-stepper" data-ap-stepper>
  <ol class="ap-stepper__steps" aria-label="Checkout progress">
    <li class="ap-stepper__step" data-state="done">
      <span class="ap-stepper__marker" aria-hidden="true"><span class="ap-stepper__num">1</span><svg class="ap-stepper__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <span class="ap-stepper__label"><span class="ap-stepper__vh" data-ap-state>Completed:</span> <span class="ap-stepper__name">Bag</span></span>
    </li>
    <li class="ap-stepper__step" data-state="current" aria-current="step">
      <span class="ap-stepper__marker" aria-hidden="true"><span class="ap-stepper__num">2</span><svg class="ap-stepper__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <span class="ap-stepper__label"><span class="ap-stepper__vh" data-ap-state>Current:</span> <span class="ap-stepper__name">Address</span></span>
    </li>
    <li class="ap-stepper__step" data-state="todo">
      <span class="ap-stepper__marker" aria-hidden="true"><span class="ap-stepper__num">3</span><svg class="ap-stepper__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <span class="ap-stepper__label"><span class="ap-stepper__vh" data-ap-state>Not started:</span> <span class="ap-stepper__name">Payment</span></span>
    </li>
    <li class="ap-stepper__step" data-state="todo">
      <span class="ap-stepper__marker" aria-hidden="true"><span class="ap-stepper__num">4</span><svg class="ap-stepper__tick" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <span class="ap-stepper__label"><span class="ap-stepper__vh" data-ap-state>Not started:</span> <span class="ap-stepper__name">Review</span></span>
    </li>
  </ol>

  <div class="ap-stepper__body">
    <section class="ap-stepper__panel" data-ap-panel="1" aria-labelledby="stepper-h-1" hidden>
      <p class="ap-stepper__kicker">Step 1 of 4</p>
      <h3 class="ap-stepper__h" id="stepper-h-1" tabindex="-1">Your bag</h3>
      <dl class="ap-stepper__facts">
        <div><dt>Block-print cotton kurta, M</dt><dd>₹1,890</dd></div>
        <div><dt>Handloom stole, indigo</dt><dd>₹1,250</dd></div>
        <div><dt>Total</dt><dd>₹3,140</dd></div>
      </dl>
    </section>
    <section class="ap-stepper__panel" data-ap-panel="2" aria-labelledby="stepper-h-2">
      <p class="ap-stepper__kicker">Step 2 of 4</p>
      <h3 class="ap-stepper__h" id="stepper-h-2" tabindex="-1">Delivery address</h3>
      <p class="ap-stepper__text">Asha Rao, 14 MG Road, Kothrud, Pune 411038</p>
      <p class="ap-stepper__note">Arrives Thursday, 9 October. Free delivery.</p>
    </section>
    <section class="ap-stepper__panel" data-ap-panel="3" aria-labelledby="stepper-h-3" hidden>
      <p class="ap-stepper__kicker">Step 3 of 4</p>
      <h3 class="ap-stepper__h" id="stepper-h-3" tabindex="-1">Payment</h3>
      <p class="ap-stepper__text">Pay ₹3,140 by UPI, card or net banking.</p>
      <p class="ap-stepper__note">Nothing is charged until you place the order.</p>
    </section>
    <section class="ap-stepper__panel" data-ap-panel="4" aria-labelledby="stepper-h-4" hidden>
      <p class="ap-stepper__kicker">Step 4 of 4</p>
      <h3 class="ap-stepper__h" id="stepper-h-4" tabindex="-1">Review your order</h3>
      <dl class="ap-stepper__facts">
        <div><dt>Items</dt><dd>2</dd></div>
        <div><dt>Deliver to</dt><dd>Pune 411038</dd></div>
        <div><dt>Pay by</dt><dd>UPI</dd></div>
        <div><dt>Total</dt><dd>₹3,140</dd></div>
      </dl>
    </section>
    <section class="ap-stepper__panel" data-ap-panel="done" aria-labelledby="stepper-h-done" hidden>
      <p class="ap-stepper__kicker">All 4 steps done</p>
      <h3 class="ap-stepper__h" id="stepper-h-done" tabindex="-1">Order placed</h3>
      <p class="ap-stepper__text">We have emailed your receipt. You can track the parcel from your orders page.</p>
    </section>
  </div>

  <div class="ap-stepper__nav">
    <button type="button" class="ap-btn" data-ap-back>
      <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m15 18-6-6 6-6"/></svg>
      Back
    </button>
    <button type="button" class="ap-btn ap-btn--primary" data-ap-next>
      <span data-ap-next-label><span>Next:</span> <span>Payment</span></span>
      <svg class="ap-btn__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m9 18 6-6-6-6"/></svg>
    </button>
  </div>

  <p class="ap-stepper__vh" role="status" data-ap-live></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    The steps are an ordered list, so their number and order are exposed, and aria-current="step" marks the current one in code, not only in looks.

  • 1.4.1 Use of Color Level A

    State is never color alone: completed steps show a tick, the current one a ring and bold name, later ones a plain number, and the connectors are solid or dashed.

  • 1.4.10 Reflow Level AA

    Below 520 pixels the row turns into a vertical list, so four steps never squeeze, truncate or scroll sideways on a phone.

  • 2.4.3 Focus Order Level A

    Back and Next send focus to the new step's heading, so Tab and reading carry on from the top of the step just shown.

  • 2.4.8 Location Level AAA

    The list, the visible Step 3 of 4 and the Next button's own name ("Next: Review") all say where the person is and what comes next.

  • 4.1.3 Status Messages Level AA

    Each move is confirmed in a polite status message, "Step 3 of 4: Payment", without depending on where focus lands.

Usage

When to use it

Use it

  • A task split into a few ordered steps, where knowing how many are left helps people decide to carry on.
  • Showing where an order or an application has got to, on a page that summarises it.

Use something else

  • More than about six steps: group them, or show "Step 4 of 9" in words instead.
  • Steps people can do in any order: use a task list with a status beside each task.
  • Progress through a single long operation: use a progress bar.

Common failures

How it usually goes wrong

  • State by color only

    A green circle for done and a blue one for current read as the same to many people. Each state here has its own shape and a hidden word for screen readers.

  • Steps as a row of divs

    Without a list, nothing says there are four steps or that this is the second. An ordered list gives both for free.

  • No aria-current

    Styling the current step bold tells sighted people where they are and nobody else. aria-current="step" says it to assistive technology.

  • Focus left on Next

    When the content changes and focus stays on the button at the bottom, a screen reader user has to hunt back up for the new step. Moving focus to its heading fixes that.

  • Steps that look clickable but are not

    Hover effects on plain list items invite clicks that do nothing. Make finished steps real links or buttons, or style them as plain text, as here.

  • A horizontal row that overflows

    Four labels in a fixed row get cut off or scroll sideways on a phone. The list turns vertical when its container is narrow.

Notes

Building it

  • This is the indicator and its navigation; for the whole form flow with fields, checks and a review step, see the multi-step form pattern.
  • The hidden words sit before each step's name so a screen reader reads "Completed: Bag"; change them in the script as steps move, along with aria-current.
  • The layout switches with a container query on the stepper, not the viewport, so it also turns vertical in a narrow sidebar.
  • If finished steps should be revisited, make each one a button that goes back to it, and keep aria-current on the list item, not the button.
  • The Next button names the step it goes to ("Next: Payment"), which helps everyone, and costs nothing.

Sources: WAI-ARIA 1.2: aria-current · W3C WAI Tutorials: Multi-page forms

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction