Search topics
Quantity steppers with unnamed buttons and no typing
The minus and plus beside a product's quantity are icon buttons with no name around a printed number, or the quantity is a number field whose keys a script cancels, so a screen reader hears only "button" and nobody can type how many they want.
<button type="button" class="step"><svg>…</svg></button> The problem
Screen reader users can't tell which button adds a mug and which takes one away, nobody can type a quantity, and where the field's keys are blocked, keyboard users can't change it at all.
A shop's page for an enamel mug at $18 has a quantity stepper above Add to bag: a round minus button, the number 1, and a round plus button. With a mouse it works: press plus three times and the number reads 4. Tab to it with a screen reader running, and you hear "button", then "button" again. The two are told apart only by their icons, a minus and a plus, and neither has a word in it. The number between them is not a Tab stop, so after a press nothing says what it is now; to find out, a screen reader user leaves the buttons and reads the line. Nobody can type into it either: ten mugs for a café take nine presses of plus.
The same shop's other build is a plain number field labelled "Quantity", with the browser's small up and down arrows at its end. A developer has cancelled every key in it except Tab, to stop people typing letters or "-3". Tab reaches it, and a screen reader reads "Quantity, spin button, 1", but typing 4 does nothing, and neither do the up and down arrow keys, which the browser would otherwise use to step the number. Only a mouse on the small arrows changes it.
Success Criterion 4.1.2 asks that every control has a name that assistive technology can read. A button takes its name from its content, and here the content is an SVG with no title and no text, so the name is empty and a screen reader has only the role to say. That is failure F68: a control with no name in code. The minus and plus are drawn, not written, and the browser can't read a shape. The number is a span, plain text with no role, so it is read only when someone reads the page line by line, never with the buttons, and nothing in the code ties it to them.
Success Criterion 2.1.1 asks that everything a mouse can do can be done from a keyboard. In the second build, the field would manage it by itself: browsers give a number field typing and the up and down arrows. But the browser does a key's work only after the page's keydown handlers have run, and preventDefault() in one of them cancels that work, so every key, the arrows included, does nothing. The spin arrows at the end of the field are drawn by the browser for a pointer and never take focus, so a mouse is the only way left to set the number. Nothing in HTML can refuse this: blocking the keys is one line of valid script.
A quantity stepper sits on the way to the basket, so one that can't be read or set stops the purchase, or sends the wrong number of items. Shops repeat the same stepper on every row of the basket, so each row adds two more buttons called nothing.
3 of 10 groups affected
- No vision (affected)
- Low vision (not affected)
- Colour vision (not affected)
- No hearing (not affected)
- Hard of hearing (not affected)
- No speech (not affected)
- Motor (affected)
- Reach, strength (affected)
- Cognitive (not affected)
- Seizures (not affected)
- Without vision Screen reader users hear "button" twice, with nothing to say which adds a mug and which takes one away, and after a press nothing tells them the new number: they have to leave the buttons and read the line to find out. Where the field's keys are blocked, they can't change the quantity at all, since its spin arrows answer only a mouse.
- Limited manipulation Keyboard and switch users can't type a quantity, so ten mugs take nine presses of plus. Where the field cancels its keys, they can't change it at all, and voice control users have no name to say for either button.
- Limited reach and strength People with tremors or weak hands tire from one press per item, and where the keys are blocked, the only way left is the field's small spin arrows, which are easy to miss.
axe reports the first build's buttons as having no name (button-name), but it can't tell that the number between them can't be typed: a span with a number in it is valid markup. A scanner sees nothing wrong with the second build at all. The markup is a labelled number field, which is correct, and only pressing the keys shows that they are blocked. EaseWeb's audit presses the arrows and types a digit in each number field that has no stepper buttons beside it, and reports a field that none of them changes. Asked to fix the scanner's finding, code assistants may name each button after its picture ("+", "minus") rather than what it does, or add the names and stop there, which leaves the number untypeable. Asked to keep letters out of a number field, they may suggest cancelling keydown, which takes the arrows and the digits with it.
Try it
Set the quantity to 4 without the mouse: Tab to the stepper, then type 4, or use its buttons or the up arrow.
The panel shows what a screen reader says. In Broken 1, what are the two buttons called, and can you type a number? In Broken 2, type 4 or press the up arrow, then click the small arrows in the field with the mouse.
Broken Can you fix this?
Enamel mug $18
Screen reader says:
(Tab to the stepper)
The fix
Name the minus and plus by what they do, and make the quantity a labelled number field that takes typing and the arrow keys.
- 1Each button in the stepper has a name in words that says what it does.
- 2The quantity is a labelled field that can be typed into.
- 3No script cancels the keys a number field takes.
Unnamed icon buttons around a printed number
<form class="buy" action="/basket" method="post"> <span class="qty-label">Quantity</span> <div class="stepper"> <button type="button" class="step" data-step="-1"> <svg class="icon" viewBox="0 0 16 16">…</svg> </button> <span class="qty">1</span> <button type="button" class="step" data-step="1"> <svg class="icon" viewBox="0 0 16 16">…</svg> </button> </div> <input type="hidden" name="quantity" value="1"> <button type="submit">Add to bag</button></form>Named buttons around a number field
<form class="buy" action="/basket" method="post"> <label for="qty" class="qty-label">Quantity</label> <div class="stepper"> <button type="button" class="step" data-step="-1"> <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">…</svg> <span class="visually-hidden">Decrease quantity</span> </button> <input id="qty" name="quantity" type="number" min="1" max="10" value="1"> <button type="button" class="step" data-step="1"> <svg class="icon" viewBox="0 0 16 16" aria-hidden="true">…</svg> <span class="visually-hidden">Increase quantity</span> </button> </div> <button type="submit">Add to bag</button></form>Each button holds only an SVG with no title, so its name is empty and a screen reader says just "button". The quantity is a span and a hidden input: it is not read with the buttons, and nothing can be typed into it.
Each button now says what it does, "Decrease quantity, button", and its icon is hidden so it adds nothing to the name. The number is a real field: it is read as "Quantity, spin button, 1", people can type 10 straight in, and the up and down arrows step it. The field is also what the form sends, so the hidden input goes, and stepUp() and stepDown() stop at min and max, so the buttons can't take the number under 1 or past 10. The design keeps its round buttons. If it shows only those, the browser's own spin arrows can be hidden with CSS; the arrow keys keep working, because they belong to the field, not to the arrows.
All three name the buttons, and the first two also make the quantity a field that can be typed into and stepped with the arrow keys. Named buttons around a number field keep the design as drawn and add typing; choose them when the large round buttons matter, as they usually do on a phone, where they are easier to hit than anything else in the stepper. The browser's number field on its own is the least code, and the direct fix for a field whose keys were blocked; choose it where a plain field fits the design, such as a basket table on a desktop page, or a trade order form where people order dozens at a time. Names on the buttons only is the quickest change and clears the scanner's finding, but leaves the number untypeable, so treat it as a step towards the first fix, not as the fix.
A few choices decide how the stepper behaves once it is in a page.
- Names in a basket: when every row has a stepper, a list of buttons reads "Decrease quantity" and "Increase quantity" over and over. Put the product in the name ("Increase quantity, Enamel mug") and in the field's label, as visually hidden text after the visible word.
- The lowest and highest quantity: leave the buttons enabled at 1 and 10, since
stepDown()andstepUp()simply do nothing there. Disabling a button while it has focus drops focus to the page; see disabled buttons that can't be found or say why. - What a press says: focus stays on the button, so a screen reader doesn't read the field's new number, though the field sits right beside the button in the Tab order. If testing shows people miss it, a short polite status such as "Quantity 4" fills the gap. Announcing what Add to bag did to the basket is another topic, confirming actions such as add to cart out loud.
- Numbers out of range: a person can still type 30 into a field whose
maxis 10. When the form is sent, the browser refuses it and says why; if your page corrects the number itself instead, say so beside the field ("Only 10 left") rather than changing it silently.
If a script cancels Tab as well as the other keys, focus can't leave the field at all, which is a different failure; see keyboard traps in editors and embedded widgets. Don't make the field readonly to keep letters out either: that takes away typing and the arrow keys just as surely as the blocked keys did, and in most browsers the spin arrows with them.
Stepper components in shop themes and design systems vary. Some name the buttons but draw the number as text; some use a field but leave the icons unnamed; some block keys to keep letters out. Check what the browser exposes for each part in its accessibility panel, not what the component's documentation promises.
Fixing this with an AI coding assistant? Get this guide as Markdown, or get every guide in your coding agent.
Design ideas
A light, modern stepper can still be read and typed into.
Four ways to draw the mug’s quantity. Click each one, then put the mouse aside and Tab to it: three can be set from the keyboard and say what they are, and one only answers a pointer.
In the design file
- Write each button’s name beside it, such as Decrease quantity and Increase quantity, or Remove for a bin.
- Draw the number as a box that can be typed into, with the caret and the focus ring in it.
- Give the quantity its label, Quantity, even where the design doesn’t show it.
- Draw the stepper at its lowest and highest quantity, with both buttons still there.
Passes
- One pill, the number typed in the middle The minus, the number and the plus share one 46px pill in a soft #f1f3f5 grey with a #8a929c edge, 3.15:1 on white, the buttons white circles inside it. The number is a field with no box of its own: a click or Tab puts the caret in it, and the whole pill takes the focus ring.
- Enamel mug $18A bin in place of the minus at 1 In a basket row, the minus turns into a bin when the quantity is 1, so the same place removes the item. Its name changes with its icon, from “Decrease quantity” to “Remove Enamel mug”, and the row that replaces the item offers Undo.
- A dropdown of quantities “Quantity 1” in a navy-edged pill, as many large shops draw it, is the browser’s own dropdown under the styling: a tap opens the system’s list, Tab reaches it and the arrow keys choose from it, and it is read with its label and value. It suits a small limit; past about ten, typing is quicker than a long list.
Fails
- Quantity 1Small chevrons stacked beside the numberWCAG 2.1.1 A compact counter: the number in a box with an up and a down chevron stacked beside it, drawn as icons that only a click answers. Tab goes straight past them, nothing can be typed, and a screen reader finds no control, so without a pointer the number stays at 1.
Verify the fix
5 checks, no mouse.
Test with: Keyboard, Screen reader, Touch
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the stepper component or the theme changes.
Limits
This is a starting pattern, not a guarantee for every application. Screen readers name a number field's role in different words, and some read its number before its label. Steppers that set a guest count, a number of nights or a time follow the same pattern, but a stepper that steps through words, such as sizes, is a different control: a select or a group of radio buttons. If your control moves along a track, see range sliders that only move by dragging; for icon buttons elsewhere on the page, such as a header's search and menu, see icon-only buttons with no accessible name. Test the full journey from product page to basket with your target assistive technology.
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.