Fix
Links with no discernible text
An icon-only basket link or an unlabelled product thumbnail still works with a mouse. To a screen reader it is a link to nowhere in particular — and that is a Level A failure sitting in your header on every page.
Who this locks out
An icon-only or image-only link announces itself as "link" and nothing else.
- Screen-reader customers scanning a product grid
- Voice-control customers who say "click basket"
- Keyboard-only customers reaching checkout
- Anyone using a browser's list of links to navigate
The transform
Before
<a href="/basket/"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6h15l-2 9H8z"/></svg></a> After
<a href="/basket/"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M6 6h15l-2 9H8z"/></svg><span class="screen-reader-text">Basket</span></a> Detection
How this is tested here
Every scan runs three independent engines and nine custom probes against your rendered pages, and records which engine flagged what. These are the real rules behind this fix family.
| Criterion | Level | Detected by | What we do |
|---|---|---|---|
| 2.4.4 Link Purpose (In Context) | A | axe-core (area-alt, link-name) · IBM Equal Access (a_text_purpose) | auto |
| 4.1.2 Name, Role, Value | A | axe-core (aria-allowed-attr, aria-braille-equivalent, aria-command-name, aria-conditional-attr) · IBM Equal Access (aria_accessiblename_exists, aria_activedescendant_valid, aria_attribute_allowed) | auto |
This family is keyed to the axe rules link-name in the
engine's own category map.
What Klarvo Access does
Klarvo Access generates an accessible name for each nameless link, applies it server-side in the HTML your store serves, then re-validates the page independently to confirm the name is really there.
Auto-applied
Generated, then applied server-side in the HTML your store actually serves — and independently re-validated in the served page before it counts as resolved.
An automated finding is evidence, not a certificate. Conformance is certified only by the professional audit — how that works.
Do it yourself
Fixing this by hand
What an empty link is, and who it shuts out
A link is empty when nothing inside it produces text that assistive technology can read out. It navigates perfectly with a mouse. It has no name. So a screen reader announces the only thing it knows — “link” — and moves on. Then “link”. Then “link”.
Three places this turns up in nearly every store:
The header. The basket, the search toggle, the account icon. Each is an inline <svg> or an icon font glyph wrapped in an anchor, with no text anywhere inside it. Because the header is on every page, one defect becomes a defect on every page of the shop.
The product grid. Each card’s thumbnail is a link to the product. If the image’s alt attribute is missing or empty, that link has nothing to name it. The card’s title beneath it is a second, separate link to the same place — so a customer hears “link”, then “Aran jumper, oatmeal”, and has to work out that the two are the same destination.
Everything peripheral. Footer social and payment icons, “back to top” arrows, carousel arrows, the language switcher.
The people this stops are specific. A customer using a screen reader who pulls up the browser’s list of links to jump around a shop page — a standard way to navigate, faster than reading — gets entries reading “link”, “link” and, where the software falls back to the address, “slash question mark add dash to dash cart equals two four one two”. Nothing says which one is the basket. A customer using voice control says “click basket”, the software finds no such name to match, and nothing happens — with no error to explain why. A customer using a switch or a keyboard tabs into the header, hears an unnamed link, and has to activate it to learn where it goes, then navigate back.
None of them abandon the store loudly. They just leave, and it looks like ordinary drop-off.
How this is detected here
This family is keyed to one rule: link-name in axe-core (version 4.12.0), which fails any anchor with an href that computes no accessible name. Alongside it, area-alt covers <area> elements inside an image map — rare in stores, common in old “shop the look” pages.
Both map to 2.4.4 Link Purpose (In Context), Level A. The same finding also engages 4.1.2 Name, Role, Value, Level A, which is where a link built as a <div role="link"> rather than an anchor gets caught.
Two engines run over your pages, independently. The second, ace, carries a_text_purpose for 2.4.4 and, for 4.1.2, aria_accessiblename_exists and aria_widget_labelled. Because both engines have a named rule for this, a nameless link is normally reported twice by two separate implementations — the finding is corroborated rather than one tool’s opinion. The third engine, HTML_CodeSniffer, reports against both criteria but does not expose stable rule identifiers we can attach a fix to, so the record you see cites axe’s.
There is no Klarvo probe in this family, and it needs none. Our own probes exist for the things off-the-shelf engines genuinely cannot see — reflow, visible focus, keyboard traps. Empty links are not one of those; they are unambiguous in the markup.
Detection also runs against the rendered page, in a real browser, not your PHP templates — so links a script injects (mini-basket contents, quick-view triggers, sticky mobile bars) are seen as your customer’s browser sees them.
What Klarvo does about it
This family is auto. Two models are used together: one reads the link — its destination, its surrounding text, the image or icon inside it — and one drafts the accessible name; the name is applied only when they agree with high confidence. Anything ambiguous is routed to the review queue for a person instead of being guessed at.
Applied means applied server-side, in the HTML your store serves. Not an overlay, not a script that patches the page after it loads. Then the page is re-validated independently, and the fix is only recorded as done when the name is present in the served markup and the rule passes. Where it cannot be confirmed, it is reported as unresolved rather than closed.
How to fix it yourself
Every fix here is the same move: give the link real text, or give it a real name.
Icon-only links: add hidden text
The most robust option, because it survives translation and never fights with the visible label:
<a class="site-header__basket" href="/basket/">
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
<path d="M6 6h15l-2 9H8z" />
</svg>
<span class="screen-reader-text">Basket</span>
</a>
aria-hidden="true" on the icon keeps it from being announced twice; focusable="false" stops older browsers putting the SVG itself in the tab order. Most WordPress themes already define .screen-reader-text. If yours does not:
.screen-reader-text {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
white-space: nowrap;
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(1px);
}
Never use display: none or visibility: hidden for this — both remove the text from the accessibility tree, which is the problem you are trying to solve.
The alternative is aria-label="Basket" on the anchor. It is valid and shorter, but remember it overrides whatever text is inside the link, so if there is visible wording, start the label with it.
Image links: the alt text is the link’s name
<a href="/product/aran-jumper/">
<img src="/wp-content/uploads/aran-jumper.jpg"
alt="Aran jumper, oatmeal" width="600" height="600">
</a>
If the card renders the thumbnail and the title inside one link — WooCommerce’s default — give the image alt="" and let the text carry the name. If your theme splits them into two links, an empty alt would recreate the empty link: combine the two into a single anchor instead. One name per destination, not two.
WooCommerce and WordPress specifics
-
Theme templates. Copy the file you need into your child theme and edit it there —
yourtheme/woocommerce/…mirrors WooCommerce’s own template path. Never edit the plugin directly. -
Product thumbnails. These take their
altfrom the image in the Media Library, so the real fix is upstream: set alt text on featured images. As a safety net infunctions.php:add_filter( 'wp_get_attachment_image_attributes', function ( $attr ) { if ( '' !== trim( $attr['alt'] ?? '' ) ) { return $attr; } $product = $GLOBALS['product'] ?? null; if ( $product instanceof WC_Product ) { $attr['alt'] = $product->get_name(); } return $attr; }, 10, 1 );It fills genuinely empty alt text only. Hand-written alt text is still better.
-
Block themes. Header and footer markup lives in the Site Editor, in template parts, not in PHP. Edit the part, not a file. An icon link built from an Image block inside a link needs alt text on that image; the Social Icons block generates names for itself.
-
Cart and Checkout blocks. These render in the browser rather than from a PHP template, so a template override will not reach them. Fix those at source — in your own block code, or by updating WooCommerce — and re-check the rendered page rather than the source.
-
The
titleattribute is not a fix. It is inconsistently exposed and invisible on touch. Use text oraria-label.
How you know it worked
Check per template, not per site: home, shop archive, a single product, basket, checkout. Links repeat by template, so one page proves one template.
- Tab through it. Keyboard only, no mouse. Every stop should have a name you can say out loud.
- Read the links list. In NVDA, Insert and F7; in VoiceOver, the rotor’s links list. Any entry reading “link”, “blank”, or a URL is still nameless.
- Check the computed name. In your browser’s accessibility panel, select the link and read the Name field. Empty means unfixed, whatever the markup looks like.
- Re-scan and compare.
link-nameshould return no instances on the templates you changed. Confirm it against the rendered page, since scripts add links after load.
A clean automated re-check is evidence that this specific defect is gone. It is not a certificate of conformance — only a human audit, of a defined scope and on a stated date, can say that.
Sources: W3C WAI — Understanding SC 2.4.4 Link Purpose (In Context) · W3C WAI — Understanding SC 4.1.2 Name, Role, Value · WCAG Technique H30: Providing link text that describes the purpose of a link for anchor elements · WCAG Technique ARIA8: Using aria-label for link purpose · MDN — ARIA: aria-label attribute · WordPress Developer Handbook — Child Themes
FAQ
Questions this raises
If I add an aria-label, does anything change for my other customers?
Nothing visible changes — it is not drawn on the page. But it replaces the link's name for assistive technology, so if the link already has visible text, begin the label with that same wording. A voice-control customer says the words they can see, and a mismatch leaves them with nothing to say.
The nameless links are output by a plugin I cannot edit. What then?
Do not edit the plugin — an update will overwrite it. Most output passes through a filter you can hook from your child theme's functions.php, and where it does not, a small block of markup in a template part usually reaches it. Klarvo Access applies the name in the served HTML instead, which is why plugin-owned markup is not a dead end.
The scan flagged links I cannot find on the page. Where are they?
Almost always an off-canvas menu, a slider that has not advanced yet, or a mobile-only header. They are in the page and reachable by keyboard even when they are not on screen, so they count. Open the page, press Tab repeatedly, and watch where focus goes.
Find out whether your store has this one.
The free scan checks your real pages — home, product, populated basket, hydrated checkout — with all three engines and all nine probes, and tells you exactly what it found and where.