Search topics
Toolbars of clickable icons a keyboard can't reach
A photo editor's tools are icons in spans with click handlers, so Tab passes the whole toolbar and nothing tells a screen reader the tools are there.
<span class="tool" data-action="rotate-left"> The problem
Keyboard and switch users can't reach or press any of a photo editor's tools, and screen reader users aren't told the tools are there or what they do.
A marketplace lets sellers fix a listing photo before it goes live. Under the photo sits a row of six icons: Rotate left, Rotate right, Flip, Zoom in, Zoom out and Reset. A seller whose photo came out sideways clicks Rotate right, and the mug stands upright. Each icon is a span holding an SVG, with a click handler on the span. Put the mouse aside and press Tab: focus goes from the page above straight to Save photo, past all six tools. No key turns the photo, so a keyboard user can only save it sideways. A screen reader reading the toolbar finds six spans with no text, no role and no name: nothing says the tools are there, that they can be pressed, or what each one does.
Success criterion 2.1.1 Keyboard asks that everything the mouse can do, the keyboard can do. Every tool works only on a click, on a span, which takes no focus. The browser turns Enter and Space into a click only on a native control, so even a span given tabindex would still ignore both keys. Turning, flipping and zooming the photo can't be done from the keyboard at all.
Success criterion 4.1.2 Name, Role, Value asks that every control's name and role can be determined by software. The tools are controls: a click on each one changes the photo. A span has no role of its own, so nothing says it can be pressed, and the icon inside is an SVG hidden from screen readers, so nothing gives the span a name either. W3C documents this as failure F59, script that makes a span or div a control without giving it a role, and more broadly as failure F15, a custom control that doesn't use the accessibility API.
Toolbars like this sit wherever a site edits something in place: photo and avatar croppers, document and PDF viewers, comment boxes, file lists with actions for what is selected. Icon tools are often drawn small and close together, which makes them hard to click for people with tremors, and some of them turn to the keyboard for exactly that reason.
2 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 (not affected)
- Cognitive (not affected)
- Seizures (not affected)
- Limited manipulation Keyboard and switch users never land on a tool, so they can't turn, flip or zoom the photo at all. Voice control users can't say "Click Rotate right", because no tool has a name, and are left to place the pointer by grid.
- Without vision Screen reader users find six empty spans with no text, no role and no name, so they can't tell that the photo can be turned, or which icon would do it.
axe has nothing to check in this toolbar. A span with a click handler has no role, so no rule asks it for a name, and nothing in the markup says it is meant to be pressed. The same span after the fix an AI may suggest, role="button", tabindex="0" and an aria-label, passes axe and Tab reaches it, but Enter and Space still do nothing, as div buttons that can't be reached with Tab shows.
The audit presses Tab through the page. An element with its own pointer cursor and an icon or words inside, in a container named as a toolbar by its class, id or role, that Tab never reaches is reported, when two or more such tools sit side by side and nothing in the row takes focus. In each role="toolbar" whose tools share one Tab stop, it focuses that tool and presses the arrow keys, Home and End; if focus never moves, the other tools can't be reached, and that is reported too. That is the other half-finished fix: the markup of the toolbar pattern, tabindex="-1" on every tool but the first, copied without the script that moves focus. A toolbar of clickable icons whose container has no telling class name, id or role is not reported as a toolbar, and no check can tell whether a name says what the icon does.
Try it
The photo came out sideways. Turn it upright using only the keyboard.
Press Tab: focus goes straight past the six tools to Save photo, and the panel below never names one.
Apply a fix: as buttons, each tool is a Tab stop; as a toolbar, Tab stops once and the arrow keys, Home and End move between the tools.
Broken Can you fix this?
Edit photo
Screen reader says:
(Tab through the editor)
The fix
Make every tool a button named for what it does.
<div class="photo-editor"> <img class="photo" src="/uploads/mug.jpg" alt="Enamel mug, front view"> <div class="toolbar"> <span class="tool" data-action="rotate-left"> <svg aria-hidden="true">…</svg> </span> <span class="tool" data-action="rotate-right"> <svg aria-hidden="true">…</svg> </span> <span class="tool" data-action="flip"> <svg aria-hidden="true">…</svg> </span> <span class="tool" data-action="zoom-in"> <svg aria-hidden="true">…</svg> </span> <span class="tool" data-action="zoom-out"> <svg aria-hidden="true">…</svg> </span> <span class="tool" data-action="reset"> <svg aria-hidden="true">…</svg> </span> </div> <button type="button">Save photo</button></div>What is wrong3 points
- The tools respond only to
click, on spans that take no focus, so no key reaches or presses them. - A span has no role, so no tool has one.
- Each span's only content is an icon hidden from screen readers, so no tool has a name.
A button for each tool
<div class="photo-editor"> <img class="photo" src="/uploads/mug.jpg" alt="Enamel mug, front view"> <div class="toolbar"> <button type="button" class="tool" aria-label="Rotate left" data-action="rotate-left"> <svg aria-hidden="true">…</svg> </button> <button type="button" class="tool" aria-label="Rotate right" data-action="rotate-right"> <svg aria-hidden="true">…</svg> </button> <button type="button" class="tool" aria-label="Flip" data-action="flip"> <svg aria-hidden="true">…</svg> </button> <button type="button" class="tool" aria-label="Zoom in" data-action="zoom-in"> <svg aria-hidden="true">…</svg> </button> <button type="button" class="tool" aria-label="Zoom out" data-action="zoom-out"> <svg aria-hidden="true">…</svg> </button> <button type="button" class="tool" aria-label="Reset" data-action="reset"> <svg aria-hidden="true">…</svg> </button> </div> <button type="button">Save photo</button></div>How it is fixed3 steps
- Make each tool a native button. Tab reaches it, Enter and Space press it, and the browser turns both into the same
clickthe script already listens for: the script doesn't change. - Name each button for what it does with
aria-label, so a screen reader says "Rotate right, button", and voice control users can say "Click Rotate right". - In the CSS, reset the button's own look to the icon's, and add a focus ring.
This fixes the Before and passes 2.1.1 and 4.1.2. Six tools are six Tab stops between the photo and Save photo.
Why this fix: Use native HTML first
Both pass. Buttons left in the Tab order are the smaller change: no ARIA role, no key handling, and every keyboard user already knows how to move through them. Neither WCAG nor ARIA asks for one Tab stop, and a row of six buttons is six presses of Tab, which is fine for an editor used now and then. The toolbar saves those presses: one Tab past the whole row, and it tells screen reader users the tools belong together. Choose it when the tools are many or used often, such as a document editor with a dozen tools, or several toolbars in a row that keyboard users would otherwise tab through on every visit. Then it needs all of it: the role, a name, one Tab stop and the arrow keys. The ARIA Authoring Practices advise the toolbar role only for a group of three or more controls.
Name each tool for what it does, in the words a tooltip or a help page would use: icon-only buttons with no accessible name covers the naming in detail. Sighted users who don't know an icon need the name too; a tooltip that shows it on hover and on focus, with the same words, helps both. A tool that stays on, such as Crop or Bold, is a toggle and needs aria-pressed, as toggle buttons that don't say whether they are pressed explains.
A tool that can't be used right now, such as Zoom out when the photo is back at its original size, can be disabled. The toolbar pattern notes that disabled tools are usually skipped by the arrow keys, but may be kept focusable when people need to find them; aria-disabled="true" does that, as disabled buttons that can't be found or say why describes. A toolbar that runs down the side of the screen takes aria-orientation="vertical", and the Down and Up arrows move between its tools instead of Right and Left.
Keep a toolbar to buttons, or controls that don't use Right and Left themselves. A text field or a slider inside it needs those keys, so the pattern advises against them, and where one can't be avoided, allows only one, as the last control in the toolbar. On a narrow screen, tools that don't fit often move into a More button: that is a menu, with its own keys, covered in action menus that open silently, out of keyboard reach. Toolbar components in libraries vary: check that the one you use makes every tool a button, names it and handles the keys, before relying on it.
Design ideas
Any toolbar style can leave every tool in reach of a key.
Five ways to draw the photo tools. Try each with the mouse, then put the mouse aside and Tab through them: three can be reached and used from the keyboard, and two only with a mouse.
In the design file
- Write each tool’s name beside its icon, even when the icon stands alone, since it becomes the name a screen reader reads and the tooltip.
- Draw the focus ring on one tool, and note whether the row is one Tab stop moved with the arrow keys or a Tab stop per tool.
- Draw a toolbar that appears on hover also as it shows on keyboard focus and on a touch screen.
- Next to anything that is dragged, such as a dial, draw the buttons that do the same without dragging.
Passes
- Icons with a word under each Each tool shows its name in small grey type under the icon, #5f6368, 6.05:1 on white, so nobody has to guess what a symbol means, and the same word is the name a screen reader reads and voice control listens for. It costs about 16px of height and wider tools; shorten a name rather than drop it.
- A floating pill of grouped tools The tools float in a white pill over the bottom of the photo, always shown, with a thin rule between the turn, flip and zoom groups. It reads as one object, so it suits one Tab stop and the arrow keys; draw the ring round the focused tool, not round the whole pill.
- 100%Zoom as a stepper that shows the level Zoom out and Zoom in sit either side of the level, 100%, in one rounded box, so people see what each press did. The level is plain text between two buttons, each with its own name, not a third control.
Fails
- Tools that fade in when the photo is pointed atWCAG 2.1.1 A frosted bar slides up over the photo on hover and hides again when the pointer leaves. Nothing else shows it, so Tab passes every tool and a touch screen has no hover to show them; show it on keyboard focus too, and keep it shown on touch screens, as the floating pill does.
Rule of thumb: Make a gesture a shortcut, never the only way
- 0°A straighten dial you dragWCAG 2.1.1 A ruler under the photo that tilts it a degree at a time as it is dragged, as phone photo apps draw it. It follows the pointer only, so no key straightens the photo; let the arrow keys move it, or put buttons beside it that tilt it a degree at a time.
Rule of thumb: Make a gesture a shortcut, never the only way
Verify the fix
4 checks, no mouse.
Test with: Keyboard, Screen reader, Speech input, Forced colors, Zoom
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the toolbar, its component or its library changes.
Limits
This is a starting pattern, not a guarantee for every application. The toolbar script is a minimal sketch: it has no disabled tools, no vertical orientation and no controls other than buttons. The typing area of an editor, and its name, are covered in rich text editors announced without their label, and a single control built from a div in div buttons that can't be reached with Tab. The same one-Tab-stop pattern, for a sidebar, is shown in tree views keyboards can't open and screen readers flatten. Test the complete journey 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.
References
- ARIA APG: toolbar (opens in a new tab)
- ARIA APG: toolbar (opens in a new tab)
- ARIA APG: keyboard-interface (opens in a new tab)
- WCAG Understanding: keyboard (opens in a new tab)
- WCAG Understanding: name-role-value (opens in a new tab)
- WCAG Technique F15 (opens in a new tab)
- WCAG Technique F59 (opens in a new tab)
- w3.org: wai-aria-1.2 (opens in a new tab)
EaseWeb service
Fixed it? Keep it from coming back.
I turn guides like this one into rules your coding agents follow and checks your CI runs. Then I retest by hand with a keyboard and screen readers, periodically.