Search topics
Add to cart buttons that change a badge and say nothing
Add to cart raises the number on the header's cart badge, but the badge is in no live region and the button keeps focus and its name, so a screen reader says nothing.
cartCount.textContent = cart.count; The problem
Screen reader users press Add to cart and hear nothing, so they can't tell whether the item went in, and press again or leave the page to check.
A homeware shop's product page shows an enamel mug, its price and an Add to cart button. In the header, a bag icon carries a small round badge with the number of items in the cart. The design keeps the page calm: no pop-up, no drawer sliding in. Press Add to cart and, a second or so later, once the shop's server has answered, the badge goes from 0 to 1. That is the whole confirmation, and in the second before it nothing shows that the press was taken. A sighted user sees the number change out of the corner of their eye. A screen reader user presses the button and hears nothing, during the wait or after it. Focus stays on the button, its name is still "Add to cart", and the badge is at the other end of the page. They don't know whether the mug went in, so they press again and now have two, or they leave the page to open the cart, check, and come back to find their place.
"1 item in your cart" is a status message: it reports the result of something the person did, and it does not take focus. Success Criterion 4.1.3 asks that such messages can be presented to assistive technology through role or properties, so that they are announced without moving focus. This is one of the W3C's own examples for the criterion: after an Add to Shopping Cart button is pressed, the text near the cart icon changes to "5 items", and a screen reader announces it. Here the badge is a plain span inside the cart link. Its number changes on screen, but no role or property tells assistive technology to read the change, which is failure F103. The button can't make up for it: it keeps focus and keeps its name, so there is nothing new on it for a screen reader to say. And a bare "1" would mean little even if it were read, which is why the W3C suggests adding words such as "in shopping cart" around the number.
The badge is placed for someone who sees the whole page at once. On a long product page, the header has often scrolled out of view by the time someone reaches the button, so even sighted users can miss the change.
2 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 (not affected)
- Seizures (not affected)
- Without vision Screen reader users press the button and hear nothing, so they can't tell whether the mug is in the cart. They press again and end up with two, or leave the product page to open the cart and check.
- Limited vision Screen magnifier users zoomed in on the button don't see a badge at the top of the page change, so they miss the only sign that anything happened.
A scan sees a button with a name and a link with a name, and passes both. It doesn't press Add to cart, since on a live shop that would fill a real cart, so it never sees the badge change, and nothing in the markup says the badge reports a result. EaseWeb's audit reports a page that has Add to cart or Copy link buttons and no live region at all as a lead to test by hand. Some AI assistants asked to make the cart accessible rename the cart link with an aria-label such as "Cart, 1 item". That improves the link's name when someone reaches it, but a new name on an element that doesn't have focus is not announced. Others move focus to the cart link, which takes people away from the product they were looking at. Only pressing the button with a screen reader running shows what is said.
Try it
Press Add to cart and listen.
The panel shows what a screen reader says. The shop takes a moment to answer: do you hear anything while it does, or that the mug went in? Then press twice quickly and watch the badge.
The fix
Write what happened into a status region that was on the page before the press.
- 1Keep the status region in the page from the start, and change only its words.
- 2Say the result in words, success or failure, without moving focus or disabling the button.
- 3Announce only what the person did: once per press, and nothing as the page loads.
<header> <a href="/" class="logo">Larkfield</a> <a href="/cart" class="cart"> <svg aria-hidden="true">…</svg> <span class="visually-hidden">Cart</span> <span id="cart-count" class="badge">0</span> </a></header><main> <h1>Enamel mug</h1> <p class="price">$18</p> <button type="button" id="add">Add to cart</button></main>A confirmation line beside the button
<header> <a href="/" class="logo">Larkfield</a> <a href="/cart" class="cart"> <svg aria-hidden="true">…</svg> <span class="visually-hidden">Cart</span> <span id="cart-count" class="badge">0</span> </a></header><main> <h1>Enamel mug</h1> <p class="price">$18</p> <button type="button" id="add">Add to cart</button> <p id="add-status" role="status"></p></main>Why this fix: Show a message next to what it is about
The number is right once the server answers, but the span is not a live region and nothing about the focused button changes, so a screen reader has nothing to announce, during the wait or after it. A second press while the first is on its way adds a second mug. If adding fails, nothing changes at all.
The p with role="status" is in the HTML from the start and empty, so the browser is already watching it when its text changes. The screen reader finishes what it is saying, then reads "Enamel mug added. 1 item in your cart." If the shop takes more than half a second to answer, "Adding the enamel mug…" comes first, so the wait is not silent either; a quick answer skips it, so people aren't told twice. Focus stays on the button, which stays enabled: a press during the wait is ignored rather than adding a second mug. Each press changes the count in the sentence, so a second press is announced too. The line sits under the button, where a magnifier user is already looking and where a sighted user who has scrolled past the header can see it, and it can stay there until the page changes. A failed add gets its own words instead of silence.
All three announce the result without moving focus. Use the line beside the button: it is the same code as the hidden region, and it gives up nothing, because the message also appears where people are looking, and it can say when adding is slow or fails. The hidden region is the right choice when the design must stay as drawn, for example in a theme you can't change, and it covers every Add to cart on the site at once. Making the count a status region is the smallest change, one wrapper and no change to the script, but it is not enough on its own: use it only when the header is the same at every width and its count is in the HTML, and still give failures a message of their own.
Other actions that only change an icon. The same failure, and the same fix, comes up wherever an action's only result is a picture.
- Copy link: an icon that turns into a tick for two seconds is not heard. Write "Link copied" into the line or the region, and say so when the browser refuses to write to the clipboard.
- Save to wishlist: a heart that fills is a toggle button, and its state belongs on the button itself with
aria-pressed, which is read on the focused button. See toggle buttons that don't say whether they are pressed. A status message is needed only for what the button can't say, such as "Saved. Sign in to keep your list." - Quick add on a product grid: every card's button says "Add to cart", so the message must name the product.
While it adds. The wait between the press and the server's answer is part of the confirmation. The line and the hidden region handle it as loading spinners that screen readers never announce does: a quick answer gets only the result, and a slow one gets "Adding…" first, from a timer that the answer cancels. Keep the button enabled and ignore presses while one is on its way. Setting disabled on the focused button looks tidy, but the browser can move focus off it: in Chrome, focus falls to the page itself and does not come back when the button is enabled again, so a screen reader user loses their place in the middle of buying. A spinner drawn on the button is fine for sighted users, as long as the button keeps its name; leave the words to the status region. Disabled buttons that can't be found or say why covers disabled controls in general.
The region must exist first. In a component framework, render the region in the layout or in the product's own markup, never inside an {#if} that appears with the message, and never in a mini cart that mounts when something is added. A region that arrives with its message is usually not announced; status messages that are shown but never announced shows that failure in full.
A cart drawer is a different design. Some shops open a mini cart panel after adding. If it moves focus into the panel and works as a dialog, that is a change of context, not a status message, and the panel's own heading tells people what happened: follow modals that leave keyboard focus behind them. A panel that slides in without taking focus is as silent as the badge, and needs one of the fixes here.
Politeness. role="status" waits until the screen reader has finished speaking, which suits a confirmation. Don't use role="alert" or aria-live="assertive" for adding to a cart; it cuts into whatever the person was listening to.
The same words twice. Two failures in a row write the same sentence, and some screen readers may say nothing when a region's text does not change. Clear the region first and write the new words after a short delay, as search and filter results that update without a word does.
Not fixes. Changing the button's text to "Added" for two seconds is read by some screen readers on the focused button and not by others, and it says nothing about the cart. Moving focus to the cart link or to a message gets it read, but takes people away from the product, and it is not what 4.1.3 asks for. An aria-label on the cart link that changes with the count is not announced, because the link doesn't have focus.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
A confirmation can be quick, and still be said.
Four ways to show that Add to cart or Copy link worked. Press each one: two put what happened into words a screen reader is also given, and two show it only as a picture.
In the design file
- Write the sentence each action confirms with, such as “Enamel mug added. 1 item in your cart.”, even where the screen shows only a tick, a number or a moving picture.
- Draw what the button shows while the shop answers, at the width of its label and still looking like a button.
- Write the message for when it fails next to the one for success.
- Write the ratios next to the confirmation’s colours, at least 4.5:1 for its words.
Passes
- A button that says Added The label turns to a small spinner and “Adding…”, then a tick and “Added” on a pale green (6.46:1) for two seconds, at the same width so nothing jumps. Screen readers read a focused button’s new label in some setups only, so the same sentence also goes into a hidden status line.
Enamel mug $18
A toast with the product’s photo A dark pill rises from the bottom with the mug’s photo and “Enamel mug added. 1 item in your cart.” in white, 14.81:1, and stays six seconds. It is drawn inside a status area that is on the page before anything is added, so it is read as it appears.Rule of thumb: Move to show a change, then stop
Fails
- The photo flies into the bagWCAG 4.1.3 A copy of the photo shrinks into the bag in half a second and the number on the bag bounces. The movement shows the change and stops, but it is the only sign: no words, so a screen reader says nothing, and with reduced motion only the number changes.
Rule of thumb: Move to show a change, then stop
Enamel mug $18 Share this mug
A Copy link icon that turns into a tickWCAG 4.1.3 The chain icon flips to a tick for two seconds, then back. Nothing is in words, so a screen reader hears nothing, and a tick alone may not say “copied” to anyone. Write “Link copied” beside it into a status line, and say so when copying fails.
Verify the fix
4 checks, no mouse.
Test with: Screen reader, Keyboard, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the header, the product template or the cart script changes.
Limits
Live regions behave differently across screen readers and browsers, especially regions inside links, moved regions and regions inside dialogs. This pattern covers one button on one product page. Naming the cart's own buttons is covered in icon-only buttons with no accessible name, and the quantity next to Add to cart in quantity steppers with unnamed buttons and no typing. A form that sends and shows its result, such as a newsletter sign-up, is a topic of its own. Test your real cart, with its drawer, quick-add buttons and phone header, with the screen readers your users use.
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.
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.