Search topics
Step indicators that mark the current step only by its look
A checkout's step indicator fills the circle of the step you are on and ticks the finished ones, but nothing in the code says which is which, so a screen reader reads every step the same way.
<li class="current"> The problem
Screen reader users can't tell which step of a form they are on, or which steps are done, because only the step indicator's fill and ticks show it.
A shop's checkout is drawn as four steps under one heading, Checkout: Cart, Delivery, Payment and Review, each a numbered circle joined to the next by a line. The design keeps the page calm and shows progress at a glance: the step you are on is a filled blue circle with its name in bold, the finished ones have a tick, and the ones to come are grey outlines. A sighted shopper on the delivery step sees straight away that they are halfway through.
A screen reader user on the same page hears "Checkout, heading", then a list: "Cart, link", "2 Delivery", "3 Payment", "4 Review". Nothing says that Delivery is the step they are on, or that Cart is done; the tick is drawn, not written. The heading reads Checkout on every step, so it doesn't tell them either. They find out where they are by reading on to the fields, and after a page reload or a Back press they have to do it again.
The indicator shows relationships: this step is the current one, these are finished, these are still to come. WCAG 1.3.1 Info and Relationships requires information shown by presentation to be available in the code, or in the text. Here it is only in the look. The fill comes from a current class and the tick from a done class, and class names mean nothing to the browser, so assistive technology has nothing to pass on. The tick itself is an icon hidden from screen readers, with no words in its place.
The heading passes. WCAG 2.4.6 Headings and Labels asks that headings describe their topic or purpose, and Checkout describes the page; it doesn't ask for a step number. Naming the step in its heading is still the plainest way to tell everyone where they are, so it is one of the fixes here: it gives the current step in text, which is what 1.3.1 needs.
The look itself passes. The current step is a filled circle with its name in bold, not a change of colour alone, so the drawing meets 1.4.1 Use of Color. Every circle's border has at least 3:1 contrast with the white page (3.03:1 for the steps still to come, 6.84:1 for the others), and the tick 5.49:1 with its pale fill, as 1.4.11 Non-text Contrast asks. Marking the step in code also meets technique ARIA26, which is sufficient for 2.4.8 Location, the Level AAA criterion about knowing where you are within a set of pages.
Checkouts, sign-ups, applications and bookings split into steps all use an indicator like this, and its look varies more than its markup: circles joined by a line, numbered tabs, a row of words with chevrons. Whichever way it is drawn, if the current step is only a style, people who can't see the style lose their place.
3 of 10 groups affected
- No vision (affected)
- Low vision (affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (not affected)
- Reach, strength (not affected)
- Cognitive (affected)
- Seizures (not affected)
- Without vision A screen reader reads every step in the indicator the same way, so the person can't tell from it which step they are on or which are done, and has to work it out from the fields on the page.
- Limited vision Under a magnifier the indicator is often out of view while the fields are filled in, and a heading that reads Checkout on every step doesn't say which step this is either.
- Limited language, cognitive and learning abilities People who lose their place in a long form need to know how far they are and how much is left. With the step shown only by a fill, anyone who misses or can't read the indicator has nothing else to go on.
A scanner sees a valid ordered list of short text and passes it. It can't tell that one item is drawn as current, because a class called current, active or is-complete means nothing to it, and it can't check that the mark moves when the step does. EaseWeb's audit reports a list marked as steps by its class or name in which one item has a class such as current or active, is drawn differently from the others, and has neither aria-current nor hidden text, when neither a heading nor the page title names the step. It sends other step lists that are drawn unevenly with nothing in the code to say why, and unmarked lists on a page whose heading or title names the step, to a person as review leads. AI code generators may reach for aria-selected="true" or role="tab" on the current step, which belong to tabs and listboxes and are ignored or misread on a list item, or turn the indicator into a role="progressbar" whose value of 2 can be read as "2 percent". Listening to the indicator on two different steps is the only way to confirm it says where you are.
Try it
Press Continue and Back to move through the checkout.
The panel reads the top of the step as a screen reader does. Can you tell which step you are on, and which are done?
Broken Can you fix this?
Reading from the top, a screen reader says:
- Checkout, heading
- list, 4 items
- Cart, link
- 2 Delivery
- 3 Payment
- 4 Review
The fix
Mark the current and finished steps in the code, or name the step in its heading, and draw the look from the same step value.
- 1Mark the current step with
aria-current="step"or hidden text, or name it and its number in the step's heading. - 2Don't leave a tick or a fill as the only sign that a step is done: say it in words.
- 3Set the look, the markers and the heading from one step value, and move them all when the step changes.
<h1>Checkout</h1><ol class="steps" role="list"> <li class="done"> <a href="/checkout/cart"> <span class="mark"><svg aria-hidden="true" viewBox="0 0 16 16">…</svg></span> Cart </a> </li> <li class="current"> <span class="mark">2</span> Delivery </li> <li> <span class="mark">3</span> Payment </li> <li> <span class="mark">4</span> Review </li></ol>Aria-current on the current step, and hidden text on the finished ones
<h1>Checkout</h1><ol class="steps" role="list"> <li class="done"> <a href="/checkout/cart"> <span class="mark"><svg aria-hidden="true" viewBox="0 0 16 16">…</svg></span> Cart<span class="visually-hidden">, completed</span> </a> </li> <li aria-current="step"> <span class="mark">2</span> Delivery </li> <li> <span class="mark">3</span> Payment </li> <li> <span class="mark">4</span> Review </li></ol>Why this fix: Style from the state, not a class
The current and done classes give the steps their look and nothing else. A screen reader reads "2 Delivery" exactly as it reads "3 Payment", and reads the finished step as "Cart, link", because its tick is hidden and has no words.
aria-current="step" tells assistive technology that this item is the current step in a process, and screen readers that support it announce it as the current step. The style now selects on the attribute, so the look and the code come from one marker and can't point at different steps. ARIA has no state for a finished step, so the tick gets its words as hidden text inside the link: "Cart, completed, link". Write that text from the same template condition that adds the done class, and translate it with the rest of the page. The design is unchanged: every circle, fill and tick looks as it was drawn.
Both say which step you are on. Use aria-current="step" with hidden text on the finished steps: it fixes the indicator itself, keeps the design exactly as drawn, and draws the look from the same markers, while the heading alone passes only as long as the steps are always taken in order. The step in the heading is still worth adding beside it. It helps people who never see the indicator, such as magnifier users looking at the fields, and people who need the count in plain words, and it costs the designer only a line of small text above the heading.
The indicator and the page around it need a few more choices.
- Lists without markers: Safari's VoiceOver can drop the list role from a list whose numbers or bullets CSS has removed, so "list, 4 items" is never said. Add
role="list"to theolto keep it. - Finished steps as links: W3C's multi-page forms tutorial suggests linking each finished step so people can go back and check it. Keep its hidden "completed" text inside the link, so it is part of the link's name. Leave steps still to come as plain text, not links that do nothing.
- Hidden text for the current step too: where the indicator comes from a component that takes text but not attributes, write ", current step" as hidden text inside the current item. The tutorial does the same, with "Current: " before the name. Set it from the same condition as the class, and translate it.
- Single-page checkouts: when Continue swaps the step without loading a page, the router must move
aria-currentand focus with it; see single-page app route changes that are never announced. A step whose change is never announced leaves a screen reader user on a button that now belongs to another step. - Page titles: putting the step in the page's title, such as "Step 2 of 4: Delivery – Checkout", tells screen reader users where they are as soon as a page loads. That is 2.4.2 Page Titled, covered in page titles that are the same on every page.
- A bar instead of circles: a progress bar for steps needs
aria-valuetext="Step 2 of 4: Delivery", or its value is read as a percentage; see progress bars and meters that screen readers can't read.
Keep the current step visible in forced colors. A Windows contrast theme replaces the circle's fill with its own colours, so the filled circle can look like the others; the bold name and the border survive. Draw the current step with more than a fill, such as a bold name or a thicker ring, and check it in a contrast theme. On a phone, designers often shrink the indicator to "Step 2 of 4" in words, which passes; shrinking it to dots alone does not.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
A step indicator can be drawn many ways and still say where you are.
Four more ways to draw the same four steps. Press Next, and Tab to a finished step: three say which step you are on in words or in code, and one only draws it.
In the design file
- Draw the three looks, finished, current and still to come, and name each one in the file.
- Give the current step more than a fill, such as a bold name or a bar under it, so it still shows in Windows high-contrast mode.
- Write the word a screen reader should say for the tick, such as “completed”, next to it.
- Draw the phone layout with the step in words, such as “Step 2 of 4”, not dots alone.
- Write the ratios next to the colours, at least 4.5:1 for the step names and 3:1 for the rings.
Passes
- The step in words, over a segmented bar “Step 2 of 4: Delivery” in bold above four rounded segments, the first two filled in #1f5f8b. The words carry the step, so the bar only repeats them, and it fits a phone where four named circles don’t.
- Cart, completed
- Delivery
- Payment
- Review
Step names in a row, with chevrons No circles at all: the four names in a line, as many shop checkouts draw them. The current one is bold with a 3px bar under it, a shape as well as a colour; finished steps are links in #1f5f8b (6.84:1), steps to come are #5b6670 (5.87:1). The current step is marked in code, as on any indicator.- Cart, completed Linen tote, $48
- DeliveryNow
- Payment
- Review
A column that shows each answer A vertical list beside the form: each finished step shows what was given under its name, “Linen tote, $48”, so the list says in words what is done and doubles as a summary, and a “Now” tag marks the current step in text everyone reads. It takes a column, so it suits a wide screen.
Fails
- Dots, the current one stretchedWCAG 1.3.1 Popular in sign-up flows: four 8px dots, the current one stretched into a 24px pill in #1f5f8b. It names no step and gives no number, on screen or in code, so a screen reader says nothing at all and nobody knows what comes next. Write “Step 2 of 4” beside the dots.
Verify the fix
5 checks, no mouse.
Test with: Screen reader, Keyboard, Forced colors
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the checkout's steps or its step component change.
Limits
This is a starting pattern, not a guarantee for every process. Screen readers word aria-current differently, some before the name and some after, and support varies between screen readers and browsers; test with the ones your visitors use. An indicator that marks the right step still leaves people lost in a process that is confusing in itself, or one that asks for the same details twice; see checkout forms that ask for the same address twice. The menu's own current-page link uses the same attribute with another value; see navigation that marks the current page only by its look.
This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.
References
- WCAG Understanding: info-and-relationships (opens in a new tab)
- WCAG Understanding: headings-and-labels (opens in a new tab)
- WAI Tutorial: forms/multi-page (opens in a new tab)
- w3.org: wai-aria-1.2 (opens in a new tab)
- developer.mozilla.org: aria-current (opens in a new tab)
- WCAG Technique G130 (opens in a new tab)
Keep this fixed as your code changes.
Turn guides like this into rules your coding agents and CI follow, then have the result retested with a keyboard and screen readers.