Search topics
Product photo thumbnails and zoom that need a mouse
A product gallery's thumbnails are images that take only clicks and its zoom follows the mouse, and even as buttons they don't say which photo is shown.
<img class="thumb" src="mug-back-small.jpg" alt=""> The problem
Keyboard users can't show any photo but the first or see its detail, and screen reader users aren't told what each thumbnail shows or which photo is on screen
A shop sells an enamel mug. Its product page has a large photo and three thumbnails under it: the front, the side and the back. The back carries small print that says "Dishwasher safe" and "350 ml", too small to read at this size. With a mouse it all works: click the last thumbnail and the back appears, move the mouse over it and it grows under the pointer until the print is easy to read.
Put the mouse aside and press Tab. Focus goes from wherever it was straight to Add to cart. The thumbnails are images with click handlers, which take no focus, so a keyboard user sees only the front of the mug. The zoom starts from the mouse moving over the photo, which the keyboard can't do, so even on the right photo the small print stays out of reach.
Some galleries get halfway: the thumbnails are real buttons, so Tab reaches them and Enter changes the photo. But their images have empty alt text, so each one reads as "button" and nothing else. The current one is marked by a border from a class, which a screen reader isn't told about, and pressing one changes the photo without a word. The zoom still needs the mouse.
Success criterion 2.1.1 Keyboard asks that everything the mouse can do, the keyboard can do. Changing the photo is done by clicking an image, which takes no focus, so Tab never stops on it and Enter has nothing to press. The zoom listens for mousemove, which no key sets off. W3C's failure F54 describes this case: event handlers specific to a pointing device are the only way to run a function. Here the function is seeing the detail in the photo.
Success criterion 4.1.2 Name, Role, Value asks that controls expose a name, a role and their state. A clickable image has no button role. A button whose only content is an image with alt="" has a role and no name. And in both, the current thumbnail is known only from the is-current class that draws its border, a state that is never exposed. Turning the images into buttons fixes the keyboard and leaves this.
Pressing a thumbnail also changes the photo somewhere else on the page while focus stays on the thumbnail, so a screen reader user may not know that anything happened. Neither criterion requires that change to be announced. As good practice, the fixes below say it in a polite live region, as the carousels guide does for a new slide.
The other photos often hold what the first one doesn't: the back, the label, the care instructions. Anyone who can't use a mouse meets only the front view, and may not learn that the rest exist.
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 The thumbnails take no focus and the zoom starts only from a moving mouse, so keyboard, switch and voice control users can't show the other photos or see the detail in them.
- Without vision As images, the thumbnails are never reached. As buttons with empty alt text, each is only "button", and nothing says which photo is on screen, so a screen reader user can't tell the photos apart or tell what a press changed.
Thumbnails built from images have no button role, so axe has nothing to check. The audit reports them when they are named as thumbnails in a class, show a pointer cursor, never take focus, sit near a much larger photo, and nothing else in the gallery can change it. That leaves out thumbnails beside previous and next buttons the keyboard can press, and FlexSlider's thumbnails, since, with one slider on the page, its default option changes the slide with the left and right keys anywhere on the page. When the thumbnails are buttons with no name, axe reports button-name, or aria-command-name for a div with role="button".
Two things take a person, so the audit sends them as leads. One is a row of thumbnail buttons in which none carries a state: code can see that no aria-current is there, not what the page does instead. The other is a photo set up for a hover zoom (the marks left by Drift, elevateZoom, jQuery Zoom, Magento's magnifier and Dawn's hover setting), with no link round it and no control near it that says it zooms or opens the photo. A zoom written by hand leaves no such mark, and no check finds it.
Adding tabindex="0" to each image as a quick fix gives a Tab stop that Enter and Space do nothing on. A zoom made to start on focus is no better: the keyboard still can't move the view to the part of the photo it wants.
What the checkers report
- axe DevTools
- ARIA commands must have an accessible name
- Buttons must have discernible text
- Lighthouse
button,link, andmenuitemelements do not have accessible names.- Buttons do not have an accessible name
- WAVE
- Empty button
Try it
Read the small print on the back of the mug, first with the mouse, then with Tab alone
Broken 1: click the last thumbnail and move the mouse over the photo. Then put the mouse aside and press Tab: focus goes straight past the thumbnails to Add to cart.
Broken 2: Tab now reaches the thumbnails, and Enter or Space changes the photo. Watch the panel: each one is only “button”, and none is said to be current. The zoom still follows the mouse.
Apply a fix. Tab to the last thumbnail and press Enter, then use the control the fix adds. Once the photo is large, the arrow keys move it.
Broken Can you fix this?
Enamel mug
$24
Screen reader says:
(Tab into the gallery, or click a thumbnail)
The fix
Let the keyboard change and enlarge the photo
Thumbnails and zoom that take only a mouse
<div class="gallery"> <div class="photo-frame"> <img class="photo" src="mug-front.jpg" alt="Front of the mug"> </div> <div class="thumbs"> <img class="thumb is-current" src="mug-front-small.jpg" alt="" data-photo="mug-front.jpg" data-alt="Front of the mug"> <img class="thumb" src="mug-side-small.jpg" alt="" data-photo="mug-side.jpg" data-alt="Side of the mug"> <img class="thumb" src="mug-back-small.jpg" alt="" data-photo="mug-back.jpg" data-alt="Back of the mug"> </div></div>What is wrong5 points
- The thumbnails are images that respond only to
click, so they have no Tab stop and the keyboard can't show another photo. - They have no role, and their alt text is empty.
- The current thumbnail is marked by the
is-currentclass, which only draws its border. - The zoom starts only from
mousemove, so the keyboard can't reach the detail it shows. - A new photo replaces the old one without a word to a screen reader.
A Zoom button over a box that scrolls
<div class="gallery"> <button type="button" class="zoom">Zoom in</button> <div class="photo-frame" role="region" aria-label="Zoomed photo"> <img class="photo" src="mug-front.jpg" alt="Front of the mug"> </div> <p class="photo-count" aria-live="polite">Photo 1 of 3: Front of the mug</p> <div class="thumbs"> <button type="button" class="thumb" aria-current="true" data-photo="mug-front.jpg"> <img src="mug-front-small.jpg" alt="Front of the mug"> </button> <button type="button" class="thumb" data-photo="mug-side.jpg"> <img src="mug-side-small.jpg" alt="Side of the mug"> </button> <button type="button" class="thumb" data-photo="mug-back.jpg"> <img src="mug-back-small.jpg" alt="Back of the mug"> </button> </div></div>How it is fixed7 steps
- Make each thumbnail a real button with its image inside. Tab reaches it and Enter and Space press it, with no key handling to write.
- Write each thumbnail's alt text to say which photo it shows. With the image as the button's only content, that text is the button's name.
- In the click handler, set
aria-current="true"on the current thumbnail and take it off the others, so a screen reader says "current" on it. - Draw the border from
[aria-current='true'], so the look and the announcement can't disagree. - As good practice, say which photo is shown in a polite live region, so a press is heard without moving focus.
- Add a Zoom in button. It makes the photo three times as wide in a box that scrolls, and gives that box a Tab stop, so the arrow keys move around the photo. Keep the button ahead of the box, so Tab meets it first.
- Leave the zoom that follows the mouse in place as a shortcut, and switch it off while the button has the photo zoomed.
This fixes both Befores and passes 2.1.1 and 4.1.2. The zoomed photo needs a large enough file behind it to keep its detail sharp at three times the width.
Why this fix: Make a gesture a shortcut, never the only way
Each one passes, and each keeps the hover zoom for people with a mouse. They differ in what the person sees when they ask for more. The Zoom button stays in place and takes the least room in the design, but the view is no larger than the photo's own box, which is small on a phone. The dialog gives the photo most of the screen, and is a usual form on product pages: WooCommerce, when a theme turns on its gallery zoom and lightbox, keeps a hover zoom and adds a control that opens the photo, and Shopify's Dawn theme opens the photo from a button by default. It costs a little more markup, and the rest of the page is out of reach until it closes. The link takes only a few lines and has little to break, and it takes the person away from the page. Choose it when there is no time for more, or as the fallback underneath a dialog, so the larger photo can still be reached if the script fails.
aria-current="true" is the state for this, as it is for the current dot in a carousel: it marks one item in a set as the current one. aria-pressed would make each thumbnail a toggle, which suggests that a second press turns it off, and aria-selected isn't allowed on a button. Shopify's Dawn theme and Splide's navigation mode both use aria-current for a gallery's thumbnails.
A thumbnail can be named in other ways, such as aria-label="Photo 3: back of the mug" with the image's alt left empty. Alt text does the same job with less, and it is the words the live region reads.
A main photo that opens larger when a bare image is clicked fails 2.1.1 in the same way as the thumbnails. Put a button beside it, as the dialog fix does, or wrap the photo in a link to the large file, which is what PhotoSwipe asks for in its markup and what Fancybox's own example shows: a link can open the lightbox when the script has loaded, and the file when it hasn't. A lens over the photo can stay, since it needs no focus of its own.
The zoom's box needs a Tab stop only while it scrolls, so the fix adds tabindex when it zooms and takes it off again. A stop that does nothing at rest is one more for a keyboard user to pass.
When a gallery has many photos, the thumbnail row may itself scroll, with its own arrows. Those arrows are buttons with names too, and a thumbnail that takes focus while out of view has to scroll into it.
Design ideas
A gallery doesn’t have to be a row of small pictures
Six ways to draw the mug’s gallery. Try each with the mouse, then put the mouse aside and press Tab: four show every photo from the keyboard, and two only with a mouse.
In the design file
- Show the current photo’s mark as a shape, such as a bar, a ring or a filled pill, not only a colour or a dimmed look.
- Write what each thumbnail shows beside it, such as “Back of the mug”, since it becomes the name a screen reader reads.
- Beside any magnifier that follows the pointer, draw a Zoom button, an Enlarge button or a link for someone with no mouse.
- Draw the focus ring on a thumbnail, with room round it so it isn’t cut off.
- Draw arrows that appear on hover also as they show on keyboard focus and on a touch screen, or keep them showing.
Passes
- Every photo on the page No thumbnails at all: the photos sit in the page, one large and two beside each other, so there is nothing to switch and no current photo to mark. Each is a link to its large file, which also gives the keyboard its way to the detail. It costs page length, so the Add to cart button needs to stay in view beside them.
- Words in pills Front, Side and Back in pills under the photo, so nobody has to tell small pictures apart, and the word on each is the name a screen reader reads. The current pill fills with navy, white on #2d3a55 at 11.36:1, a solid fill against the others’ outline, not just a new hue.
- Photo 1 of 3Arrows round a count No row of pictures to fit on a phone: two 36px round arrows either side of “2 / 3”. The count says in plain figures which photo is shown, and a polite live region reads it when it changes. Nobody can jump straight to a given photo, only step to it, so keep it to a few.
- A rail with a bar on the current one Thumbnails down the side of the photo, the current one with a 4px navy bar along its left edge, at 10.22:1 on the thumbnail’s #f1f3f6 tint. The others stay at full strength, so they can still be told apart. The rail folds under the photo on a phone.
Fails
- Photo changes as the mouse rests on a thumbnailWCAG 2.1.1 Quick and effortless with a mouse: there is nothing to click. But there is nothing to press either, so Tab passes the thumbnails by and the keyboard stays on the first photo.
Rule of thumb: Make a gesture a shortcut, never the only way
- Arrows that come out on hoverWCAG 2.1.1 A clean photo until the mouse arrives. The arrows are real buttons, but while they are hidden they take no focus, so Tab passes them by and the keyboard never sees another photo. Show them on focus too, or keep them showing.
Rule of thumb: Make a gesture a shortcut, never the only way
Verify the fix
4 checks, no mouse.
Test with: Keyboard, Screen reader, Forced colors
Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the gallery, its library or its theme changes.
How screen readers handle aria-current="page"
Limits
This is a starting pattern, not a guarantee for every application. It covers how the thumbnails and the zoom are reached and what the thumbnails say. A gallery built as slides with arrows and dots is covered in carousels that keyboard and screen reader users can't use. A lightbox has its own needs for focus and closing: see modals that leave keyboard focus behind them. A photo viewer that turns or zooms by dragging, pinching or tilting is in pinch, drag and tilt gestures with no button. What each photo's alt text should say is a matter for the photo: this guide only asks that a thumbnail's alt text tells the photos apart, and that the large photo's stays in step when it changes.
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
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.