( Operable / WCAG 2.4.4, 4.1.2 )
Link must have discernible text
What is this issue?
An <a> element that has a working href resolves to an empty accessible name through the standard name computation: no text content between its tags, no aria-label, no aria-labelledby, and (if the link’s only content is an <img>) no alt attribute on that image either. The link is fully functional and goes somewhere real; the entire failure is that nothing identifies it.
This is distinct from a link with vague text like “click here,” which at least has something to announce. Here there’s nothing at all, so some browsers fall back to reading the raw href URL instead, which is rarely meaningful on its own.
Why does this matter?
A link’s core job is to tell someone, before they activate it, roughly where it goes. A link with zero discernible text fails that job completely: a screen reader user has nothing to evaluate except a bare “link” announcement, or a URL fragment that reads like “https colon slash slash shop dot example dot com slash p slash 48213,” which conveys almost nothing useful read aloud.
Icon-only navigation is the most common source: a social media row of icon links, a product grid where the entire card is wrapped in one link with no accessible text, a header logo link with an image and no alt. Each individually is a small gap, but a page with several of these in a row produces a screen reader experience of “link… link… link…” with no way to tell them apart without following each one blind.
Who is affected?
- Screen reader users: hear only “link” or a raw, hard-to-parse URL fragment, with no way to evaluate the destination or distinguish one unnamed link from another nearby.
- Voice control users: target links by speaking their visible or accessible name, such as “click Home.” A link with no name at all can’t be targeted by a spoken command regardless of how recognizable its icon looks visually.
What users experience
Priya uses VoiceOver on her Mac to browse an online store’s homepage. She swipes to the site’s Twitter icon in the footer, and VoiceOver announces “link, twitter dot com slash example store”, reading out the full raw URL character group by character group, since the <a> wraps only an icon image with no alt. She has no faster way to identify it as “Twitter” than listening to the entire URL announcement, something a sighted user gets instantly from the recognizable bird icon.
How do I fix it?
Add text content inside the <a> element that describes its destination, or meaningful alt text on any image inside it. This works because it gives the accessible name computation something real to find at its first, most reliable source: actual text content, rather than requiring an ARIA override.
When the link’s content is purely an icon and adding visible text would break the design, put the descriptive text in the image’s alt attribute instead: the browser uses that as the link’s accessible name automatically, since the image is the link’s only content. Reach for aria-label on the <a> element itself only when neither approach fits, such as a link wrapping multiple elements where no single alt attribute would cover the whole thing.
Code Examples
<a href="/profile"></a>
<a href="/twitter">
<img src="/icons/twitter.svg">
</a><!-- Method 1: visible text (preferred) -->
<a href="/profile">Your profile</a>
<!-- Method 2: alt text on the link's only content -->
<a href="/twitter">
<img src="/icons/twitter.svg" alt="Follow us on Twitter">
</a>
<!-- Method 3: aria-label when the link wraps more than one element -->
<a href="/twitter" aria-label="Follow us on Twitter">
<img src="/icons/twitter.svg" alt="">
<span aria-hidden="true">Twitter</span>
</a>Method 1 gives the link a real text node, the simplest and most reliable source for an accessible name. Method 2 relies on the fact that an image’s alt text becomes a link’s accessible name automatically when the image is the link’s only content; no extra attribute needed. Method 3 uses aria-label to name the whole link when it wraps more than one child, marking the redundant visible “Twitter” text aria-hidden so it isn’t announced twice alongside the label.
Framework Examples
Icon-link components are the most common source of this failure in component-driven UIs: a shared SocialLink component that maps a platform name to an icon but never surfaces that platform name to assistive technology produces a silently broken link every time it’s rendered.
function SocialLink({ platform, href, icon }) {
return (
<a href={href} aria-label={`Follow us on ${platform}`}>
<span aria-hidden="true">{icon}</span>
</a>
);
}
// <SocialLink platform="Twitter" href="/twitter" icon={<TwitterIcon />} />
Deriving the aria-label from the same platform prop that already drives which icon renders means the accessible name can never drift out of sync with which icon is actually shown; a single source of truth instead of a second string a developer has to remember to keep updated separately.
Common Mistakes
Mistake: “The icon is universally recognizable, so it doesn’t need alt text.” Icon recognizability is a purely visual property and has no bearing on what a screen reader announces. A Twitter bird icon and a completely blank image produce the identical accessible name, none, unless the image or its wrapping link explicitly carries one.
Mistake: “I gave the image alt="" to mark it decorative, so the link is fine.” Marking an image alt="" is correct when the image is genuinely decorative and there’s other text nearby giving the link a name. But if the image is the link’s only content, an empty alt removes the image from the accessibility tree entirely, leaving the link with nothing: the opposite of the intended fix.
Mistake: “The link goes somewhere obvious from the URL, so the missing text doesn’t matter.” A URL being self-explanatory to a developer reading source code says nothing about what a screen reader announces to a user, which is either silence or a raw, unparsed string like “shop dot example dot com slash p slash 48213”; neither is a substitute for real link text.
How RedFlag Detects This
Automated: axe-core rule, runs on every scan. RedFlag calls axe-core’s link-name rule as part of every scan, restricted to the WCAG 2.0/2.1/2.2 A and AA rule set. The rule inspects every <a> element with an href and computes its accessible name using the standard algorithm (text content, then any contained image’s alt, then aria-label, then aria-labelledby), flagging any link that resolves to an empty string.
False negative: axe-core confirms a name exists; it cannot judge whether that name accurately describes the destination: a link with aria-label="Twitter" pointing at an unrelated page still passes this check. False positive: none typical for this check, since resolving to a non-empty accessible name is a binary condition axe-core evaluates reliably from the DOM. Manual step: for every flagged link, confirm the fix you add actually names the real destination, not just any non-empty string that satisfies the automated check.
Manual Testing
- Open the page in Chrome or Firefox with NVDA or JAWS running, or Safari with VoiceOver.
- Pull up the links list (NVDA/JAWS: Insert+F7, then filter to Links; VoiceOver: open the Rotor and select Links).
- Read through every entry: each should announce meaningful text, not a bare “link” and not a long raw URL fragment.
- Pay particular attention to icon-only links (social icons, logo links, image-only navigation) since these are where this failure concentrates.
Related WCAG Success Criteria
2.4.4 Link Purpose (In Context): The purpose of each link must be determinable from its text alone or combined with its programmatic context. A link with zero discernible text fails this at its most basic level; there’s no text at all to determine purpose from.
4.1.2 Name, Role, Value: Every interface component must expose a name, role, and current value to assistive technology. An empty link has a role and a destination but no name, which is the specific way this rule fails 4.1.2.
Related Issues
Button or link has no accessible name covers the identical failure on <button> elements: the same missing-name problem on a different interactive element type.
Link text is generic and gives no context is the less severe sibling of this rule: a link that has text, just text too vague to be useful, rather than no text at all.
Link is not distinguishable from surrounding text covers a different link-purpose failure in the same criterion family: a link with perfectly good text that’s visually indistinguishable from the paragraph around it.
Alt text is a raw filename covers the specific case where an image inside a link does have alt text, but that text is a meaningless filename rather than a real description.
Multiple links with the same text point to different destinations covers a related link-purpose failure where several links do have text, but the same text on links going to different places defeats the same purpose this rule protects.
References
- W3C Understanding 2.4.4: Link Purpose (In Context)
- W3C Understanding 4.1.2: Name, Role, Value
- W3C Technique H30: Providing link text that describes the purpose of a link
- WebAIM: Links and Hypertext: Link Text
Frequently asked questions
Does an image inside a link need its own alt text if the link has aria-label?
No, but give it an empty alt (alt="") rather than leaving it off entirely. When a link has an aria-label, that value becomes the entire accessible name and any image alt text inside is ignored, but an image with no alt attribute at all can still trigger separate accessibility warnings for the image itself.
Does this rule catch a link with only whitespace as its text content?
Yes. The accessible name computation trims whitespace before checking for empty content, so a link containing only spaces, a non-breaking space entity, or a line break with no real text is treated identically to a completely empty link and gets flagged the same way.
Is a link with an empty aria-label worse than no aria-label at all?
They produce the same result, an empty accessible name, but an empty aria-label="" is worse to debug, since it looks intentional in the source code. A developer scanning the markup might assume the link is already handled and move on, while a completely missing aria-label at least makes the gap visible.
Does RedFlag treat this the same as a generic link like "click here"?
No, they are different failures at different severities. This rule covers links with zero discernible text: nothing for a screen reader to announce at all. Link text is generic and gives no context covers links that do have text, just text too vague to be useful, which is a less severe problem.
What does a screen reader actually say for a completely empty link?
Most screen readers announce just the role and the destination URL in some form, such as "link, example.com slash products slash 4821", a raw, often meaningless URL fragment read character by character, which is arguably worse than silence.