Plain WooCommerce variation dropdowns are ugly, so almost every store replaces them with color or size swatches, usually through a free plugin or a theme’s built-in swatch feature. Visually, it’s a clear win. Under the hood, most of these swatches are built the same broken way: a grid of <div> or <span> elements with a background color and a click handler, and nothing else.
That works fine with a mouse. It’s often invisible to a keyboard, and silent to a screen reader.
Why Swatches Break Where Dropdowns Didn’t
A native HTML <select> element comes with keyboard support and screen reader announcements built in for free. Tab to it, arrow keys change the value, and a screen reader announces the option you land on. Browsers have handled this correctly for decades.
Swatches throw that away. Most swatch plugins render each option as its own clickable element styled to look like a color chip or a size button, but built from a generic <div>, not an <input> or a <button>. A <div> has no default keyboard behavior and no default accessible name. Unless the plugin developer explicitly added tabindex, a role, and an aria-label or equivalent, that swatch simply does not exist for anyone not using a mouse.
How to Test Your Store’s Swatches in Two Minutes
You don’t need a screen reader installed to catch the most common failure. Start with the keyboard.
- Open any variable product page on your store, one that uses color or size swatches
- Click somewhere neutral on the page, like the page title, to set focus there
- Press Tab repeatedly and watch where the visible focus outline goes
- If focus skips over the swatches entirely and lands on the Add to Cart button, or on a swatch but with no visible outline, that’s a fail
- If focus does land on a swatch, try arrow keys and Enter or Space to change the selection
Then check what actually gets announced. If you have a screen reader available, VoiceOver on Mac (Cmd+F5) or NVDA on Windows (free), turn it on and tab through the same swatches. Listen for whether it announces anything at all, whether it says the color name or just “clickable” with no label, and whether it announces which option is currently selected.
A passing swatch announces something like “Red, selected, button” as you move through the options. A failing one is either silent or announces nothing more specific than “button.”
The Three Failures I See Most Often
1. No Keyboard Access At All
The swatch grid is entirely mouse-only. Tab jumps straight from the product title to the Add to Cart button, skipping variation selection entirely. A keyboard user literally cannot choose a color or size, which means they cannot buy a variable product on your store at all.
2. Focusable but Unlabeled
Tab reaches the swatch, a focus outline appears, but a screen reader announces nothing useful, sometimes just “button” or the raw hex code of the background color. The user knows something is focused. They have no idea it represents “Ocean Blue” or that selecting it changes the product they’re about to buy.
3. Selection State Isn’t Announced
This one is subtle and still common even on swatches that otherwise work. The user can tab to a swatch and activate it, the visual state clearly changes, a border or checkmark appears, but nothing is announced to confirm the selection happened. A sighted mouse user sees the change instantly. A screen reader user has to guess, or go hunting elsewhere on the page to confirm which variation is now selected.
This maps directly to WCAG 4.1.2, Name, Role, Value: every UI component needs a programmatically determined name, role, and current state, and that state has to update and be exposed when it changes.
How to Fix It: Build Swatches as Real Form Controls
The fix isn’t to abandon swatches, it’s to build them on top of real form semantics instead of styled <div>s. The pattern that works:
<fieldset>
<legend>Color</legend>
<input type="radio" id="color-red" name="color" value="red" class="swatch-input">
<label for="color-red" class="swatch-label swatch-red">
<span class="visually-hidden">Red</span>
</label>
<input type="radio" id="color-blue" name="color" value="blue" class="swatch-input">
<label for="color-blue" class="swatch-label swatch-blue">
<span class="visually-hidden">Blue</span>
</label>
</fieldset>
.swatch-input {
position: absolute;
opacity: 0;
pointer-events: none;
}
.swatch-label {
display: inline-block;
width: 32px;
height: 32px;
border: 2px solid transparent;
border-radius: 50%;
cursor: pointer;
}
.swatch-input:focus-visible + .swatch-label {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
.swatch-input:checked + .swatch-label {
border-color: #000;
}
This gets you keyboard access, arrow-key navigation between options within the fieldset, and selection state, “checked”, announced automatically, all for free, because it’s built on native <input type="radio"> elements instead of reinvented from scratch. The <legend> gives the whole group a name (“Color”), and the visually-hidden label text gives each individual swatch a name a screen reader can announce, even though sighted users just see the color chip.
Why This Keeps Breaking Even After You Fix It Once
Here’s the part that makes this worse than a one-time fix: most WooCommerce swatch plugins render their markup dynamically, and a plugin update can silently revert your fix. If you hand-edit the plugin’s template files directly, the next update overwrites your changes. If you use a child theme override, it works until the plugin changes its internal markup structure, which happens more often than you’d expect for something this visually simple.
Native WooCommerce variation swatches, added in recent WooCommerce core updates, have their own accessibility gaps too, and core updates carry the same risk of quietly changing markup.
How the Accessibility Fixer Plugin Handles This
Rebuilding swatch markup by hand, then re-checking it after every plugin and theme update, isn’t a realistic ongoing process for most store owners. The WooCommerce Accessibility Fixer detects common swatch plugin markup at runtime and patches in the missing keyboard support, labels, and selection-state announcements without replacing your theme’s visual styling.
Because the fix runs at runtime instead of living in a template file you have to remember to re-apply, it keeps working after your swatch plugin or WooCommerce itself updates. You keep the visual design your swatches already have. The parts a screen reader and keyboard need are added on top.
Related Reading
- How to Fix Keyboard Navigation in WooCommerce
- WooCommerce Checkout Accessibility: Fix the 5 Issues That Kill Sales
- 10 Common Accessibility Issues on E-commerce Sites
Quick Checklist
- Tab through a variable product page and confirm focus actually reaches the swatches
- Confirm each swatch has a visible focus outline
- Turn on VoiceOver or NVDA and confirm each swatch announces a real name, not just “button”
- Confirm selecting a swatch announces the change, not just a visual update
- Re-test after any swatch plugin, theme, or WooCommerce core update
- Consider the WooCommerce Accessibility Fixer if you’d rather not maintain this fix by hand
If your store sells variable products with color or size options, this is worth two minutes of testing today. A customer who can’t select a variation can’t complete an order, no matter how good the rest of your checkout is.