Skip to main content

The "Read More" Link Trap: How to Fix Ambiguous Link Text

Benjamin Morton

A blog index with ten cards, each ending in a "Read more" link, looks tidy. Sighted users see the card title above each link and never think about it. A screen reader user who pulls up a list of the page's links hears "Read more, Read more, Read more" ten times, with nothing to tell them apart.

It's one of the most common link problems on the web, and one of the easiest to miss. We built a test page with six versions of the "Read more" card, from the plain broken one to the correct fixes, and ran axe-core against it with the tags a typical WCAG 2.2 AA scan uses. It reported zero issues for all six. This guide covers why that happens, what WCAG actually requires, and the four fixes that work, plus two that look right but don't.

TL;DR: In February 2026, 15.2% of the top million home pages had ambiguous link text such as "click here" or "more", averaging 5.3 on each affected page (WebAIM Million, 2026). A default axe-core WCAG AA scan won't catch it: the link-name rule only checks that a name exists, and the rule that compares identical link names is Level AAA and off by default. Fix it by making the link's accessible name say where it goes, using the card heading as the link, visually hidden text, or an aria-label that starts with the visible words. aria-describedby and title don't change the name, so they don't fix it.

What Is the "Read More" Link Trap?

It's a link whose visible text doesn't say where it goes, repeated across a page so that several links share the same name but lead to different places. "Read more", "Learn more", "Click here", "Continue" and "Details" are the usual suspects. WebAIM's 2026 analysis of the top one million home pages found ambiguous link text like "click here", "more" and "continue" on 15.2% of them, up from 13.7% in 2025 and down from 23.2% in 2020 (WebAIM Million, 2026).

Home Pages With Ambiguous Link Text 23.2% 2020 13.7% 2025 15.2% 2026 Source: WebAIM Million, February 2026 (top 1,000,000 home pages)

The number matters less than who it hurts. The phrase comes from visual design: a heading and a summary sit above the link, so "Read more" reads fine to someone looking at the card. That context isn't attached to the link itself, and three groups lose it:

  • Screen reader users browsing a links list. NVDA's Elements List (NVDA+F7), JAWS's Links List (Insert+F7) and the VoiceOver rotor (VO+U) each pull links out of the page (NV Access; Freedom Scientific; Apple). Out of context, ten identical names are useless.
  • Keyboard and screen reader users tabbing through links. Each stop announces "Read more, link" with no destination.
  • Voice control users. People using speech input activate links by saying their visible label (W3C, Understanding 2.5.3). With ten links sharing one visible label, saying that label no longer identifies a single link. Apple Voice Control users can fall back to saying "Show numbers" and picking a link by number (Apple).

To be accurate about scale: link lists aren't most screen reader users' first move. In WebAIM's 2024 survey of 1,539 screen reader users, 4.8% said they'd navigate through the page's links first when looking for information on a lengthy page, against 71.6% who'd use headings. But "links or buttons that do not make sense" ranked third among the problems respondents found most troublesome, after CAPTCHA and interactive elements (WebAIM Screen Reader Survey #10, 2024). The pain is real even when the navigation method isn't the most common.

What Does WCAG Actually Require?

Less than most people assume at Level A, which is why "Read more" survives audits. Two success criteria cover link purpose, and one more covers how you fix it:

Criterion

Level

What it requires

2.4.4 Link Purpose (In Context)

A

Purpose clear from the link text alone, or from the link text plus its programmatically determined link context

2.4.9 Link Purpose (Link Only)

AAA

A mechanism exists so purpose is clear from the link text alone

2.5.3 Label in Name

A

The accessible name contains the visible text label

The phrase doing the work in 2.4.4 is "programmatically determined link context". W3C's examples of that context in HTML are text in the same paragraph, list item or table cell as the link, or in an associated table header cell. A note adds that screen readers can also give context from the surrounding sentence (W3C, Understanding 2.4.4).

So a sentence like "Our pricing changed in March. [Read more]" inside one paragraph can meet 2.4.4 at Level A. Cards are less clear-cut. If each card is a list item (<li>) that contains its title and summary, W3C technique H77, link text plus its enclosing list item, is sufficient. If the card is an <article> or <div> and the only context is the heading above the link, the matching technique is H80, link text plus the preceding heading, and W3C lists H80 as advisory, not sufficient. That leaves the common <article> card without a documented sufficient technique to point to for a 2.4.4 pass. Techniques aren't the only way to conform, but you'd be arguing a pass rather than citing one. At Level AAA, W3C names this pattern directly: failure F84 is using a non-specific link such as "click here" or "more" without a way to change it to specific text (W3C, Understanding 2.4.9).

Citation capsule: WCAG 2.2 SC 2.4.4 (Level A) lets a link's purpose come from its text plus its programmatically determined context, which W3C illustrates in HTML as text in the same paragraph, list item or table cell, or an associated table header cell (W3C). A "Read more" link in an <article> card whose only context is the heading relies on technique H80, which W3C lists as advisory rather than sufficient, so the fix is to put the destination into the link's accessible name.

Don't aim for "technically passes 2.4.4". W3C's own intent for the criterion is written around the links-list use case: link text that is as meaningful as possible helps users picking from a list of links. Put the destination in the name and the question goes away.

Why Doesn't axe-core Catch "Read More" Links?

Because the rules that would catch it don't run by default. We tested this directly. We built one page with three blog cards and six versions of the "Read more" link, then ran axe-core 4.11.1 through @axe-core/playwright in Chrome with the tags a typical WCAG 2.2 AA scan uses (wcag2a, wcag2aa, wcag21a, wcag21aa, wcag22aa). We also read each link's accessible name from Chrome's accessibility tree, and re-ran the two relevant axe rules explicitly.

Six "Read More" Links, One Default Scan, Zero Issues Link version Accessible name Default AA scan Rules enabled Plain "Read more" Read more No issues Needs review Visually hidden text Read more about colour contrast No issues Pass aria-label, starts "Read more" Read more about colour contrast No issues Pass aria-label, no "Read more" colour contrast guide No issues Violation (2.5.3) aria-describedby Read more No issues Needs review title attribute Read more No issues Needs review "Rules enabled" runs identical-links-same-purpose and label-content-name-mismatch explicitly. Source: A11yFlow test, axe-core 4.11.1 via @axe-core/playwright in Chrome, September 2026

The same results as a table:

Link version

Accessible name

Default AA scan

Link rules enabled

Plain "Read more"

Read more

No issues

Needs review

Visually hidden text

Read more about colour contrast

No issues

Pass

aria-label starting "Read more"

Read more about colour contrast

No issues

Pass

aria-label without "Read more"

colour contrast guide

No issues

Violation (2.5.3)

aria-describedby

Read more

No issues

Needs review

title attribute

Read more

No issues

Needs review

Three things explain the blank column:

  1. link-name only checks that a name exists. It's the rule behind "Links must have discernible text", and it catches empty links, a problem WebAIM found on 46.3% of home pages in 2026 (WebAIM Million, 2026). "Read more" is discernible text, so it passes.
  2. identical-links-same-purpose is Level AAA and disabled by default. It's tagged wcag2aaa and wcag249, and it only ever returns "needs review", never a violation (axe-core rule descriptions). An AA scan never runs it. A plain axe run that includes the wcag2aaa tag does run it, but even then it only asks a person to review.
  3. label-content-name-mismatch is experimental. In axe-core 4.11.1 it carries the experimental tag, so a run filtered to WCAG tags skips it. Enabled, it was the only rule that flagged a real failure: an aria-label that dropped the visible words.

Lighthouse does look for this, but in its SEO category, not accessibility. Its "Links do not have descriptive text" audit matches the whole link text against a list of generic phrases. The English list in the source code includes "read more", "learn more", "click here" and "more", so a bare "Read more" is flagged while "Read more about pricing" passes (Lighthouse source; Chrome for Developers). That's a useful quick check with two blind spots. It's exact-match only, so "Find out more" or "View details" slip through. And it reads the link's rendered text (innerText), not its accessible name, so a link fixed with aria-label or aria-labelledby still gets flagged. Visually hidden text does count, so that fix passes. WebAIM's own count uses its WAVE engine, which reports suspicious link text as an alert for a human to check rather than an error (WebAIM Million, 2026; WAVE API documentation).

It's a clear case of why automated scans are a floor, not a ceiling. A clean axe accessibility score tells you nothing about your link text. (It's also a reminder to check what your scanner's rule set actually includes before trusting a zero.)

How Do You Fix Ambiguous Link Text?

Make the link's accessible name say where it goes. The accessible name is what screen readers announce, what links lists display, and what voice control matches against. There are four good ways to set it, in rough order of preference. Only the first one also helps voice control users: the other three fix the name, but ten links still show the same visible "Read more", so a spoken "click Read more" still matches all of them.

1. Make the Heading the Link

The best fix for cards is to delete the "Read more" link entirely and link the title instead. The heading already describes the destination, screen reader users navigating by heading land right on the link, and you remove a redundant tab stop. NN/g lists making the heading the only link as one of three alternatives to "Learn more" links, alongside descriptive link text (NN/g, 2015).

<article class="card">
  <h3>
    <a href="/blog/color-contrast-wcag-developer-guide">
      Colour Contrast in WCAG 2.2: A Developer Fix Guide
    </a>
  </h3>
  <p>Ratios, tokens and the CSS that fixes the most common failure.</p>
</article>

If design wants the whole card clickable, don't wrap the card in an <a>. That turns every piece of card content into one long link name; Adrian Roselli's example takes about 25 seconds to read before it's even announced as a link (Adrian Roselli, updated 2025). Instead, stretch the heading link over the card with a pseudo-element, the pattern described by both Roselli and Heydon Pickering in Inclusive Components:

export function PostCard({ post }) {
  return (
    <article className="card">
      <h3>
        <a href={`/blog/${post.slug}`}>{post.title}</a>
      </h3>
      <p>{post.excerpt}</p>
      {/* Visual cue only: not a second link */}
      <span className="card-cue" aria-hidden="true">Read more</span>
    </article>
  );
}
.card {
  position: relative;
}

.card h3 a::after {
  content: '';
  position: absolute;
  inset: 0;
}

.card:focus-within {
  outline: 3px solid currentColor;
  outline-offset: 4px;
}

The one link now has the title as its name, the whole card is clickable, and the visible "Read more" survives as decoration. Pickering notes the trade-off: the overlay makes it harder to select text inside the card. If users need to copy card text, keep the click area to the heading.

2. Add Visually Hidden Text

When the visible "Read more" has to stay a real link, add the destination as text that's hidden visually but still in the accessibility tree. This is W3C technique C7, sufficient for both 2.4.4 and 2.4.9 (W3C, C7). The GOV.UK Design System uses this for its "Change" links, so screen reader users hear "Change name" or "Change date of birth" (GOV.UK Design System).

<a href="/blog/color-contrast-wcag-developer-guide">
  Read more<span class="visually-hidden"> about colour contrast</span>
</a>
.visually-hidden {
  clip-path: inset(50%);
  height: 1px;
  overflow: hidden;
  position: absolute;
  white-space: nowrap;
  width: 1px;
}

Keep the visible words first, so the name starts with what's on screen. In our test the name came out as "Read more about colour contrast", and identical-links-same-purpose passed. Don't use display: none or visibility: hidden for the extra text; W3C's C7 notes those hide it from assistive technology too.

3. Point aria-labelledby at the Heading and the Link

aria-labelledby builds the name from other elements by ID, so you can reuse the card title instead of repeating it. It's W3C technique ARIA7, listed for 2.4.4 (W3C, ARIA7). Include the link's own ID so the visible words stay in the name:

<h3 id="post-12-title">Colour Contrast in WCAG 2.2</h3>
<a href="/blog/color-contrast-wcag-developer-guide"
   id="post-12-more"
   aria-labelledby="post-12-more post-12-title">Read more</a>

Per the accessible name spec, the name becomes "Read more Colour Contrast in WCAG 2.2". W3C's ARIA8 technique says that when the descriptive text is already visible on the page, aria-labelledby should be used instead of aria-label (W3C, ARIA8), because it reuses that text. The catch is fragility: rename an ID and the name quietly breaks, and no scanner will tell you.

4. Use an aria-label That Starts With the Visible Text

aria-label replaces the link's content as its name, since the accessible name calculation checks aria-labelledby and aria-label before it falls back to the element's content (W3C, accname 1.2). It's W3C technique ARIA8, and W3C recommends starting the label with the link's visible text (W3C, ARIA8).

<!-- Good: name contains and starts with the visible label -->
<a href="/blog/color-contrast-wcag-developer-guide"
   aria-label="Read more about colour contrast">Read more</a>

<!-- Fails 2.5.3: the visible words are not in the name -->
<a href="/blog/color-contrast-wcag-developer-guide"
   aria-label="Colour contrast guide">Read more</a>

This is where people trade one bug for another. The second link has a perfectly descriptive name, but a voice control user who says "click Read more" is saying words that aren't in it. WCAG 2.5.3 Label in Name covers this case, and W3C lists it as failure F96 (W3C, Understanding 2.5.3). In our test it was the only version any axe rule called a violation, and only once we enabled the experimental rule.

Use aria-label when the destination text isn't visible anywhere nearby to point aria-labelledby at. Otherwise prefer visually hidden text: it's ordinary content sitting next to the visible words, so it can't drift out of sync with them the way a separate attribute can.

Which Fixes Look Right But Don't Work?

Two common attempts leave the name as "Read more", and our test confirmed both in Chrome's accessibility tree.

aria-describedby adds a description, not a name. The accessible name and the accessible description are separate properties: the name is "a short label that provides information about the purpose of the object", the description "further clarifies" it (W3C, accname 1.2). Pointing aria-describedby at the card heading gave our link a description, but its name stayed "Read more", and identical-links-same-purpose still asked for a review. Screen readers and links lists work from the name.

The title attribute doesn't fix it either. Our link with title="Read more about colour contrast" still had the name "Read more". Technique H33, supplementing link text with title, is on W3C's list, but W3C itself warns authors to "use caution" because of "extensive user agent limitations" in exposing title, and says C7 or H30 are preferred (W3C, H33).

The same logic applies to ARIA more broadly: an attribute that looks like it adds information doesn't help unless it changes what assistive technology actually exposes.

How Do You Find Ambiguous Links on Your Own Site?

Scan with the rules switched on, then check the flagged links yourself. Three quick methods, from fastest to most thorough:

  1. Run Lighthouse's SEO audit. "Links do not have descriptive text" catches exact generic phrases, "read more" included. It's a two-minute check, but it misses anything not on its list.
  2. Enable the AAA and experimental link rules in axe. In a Playwright test, run the two rules by name so tag filters don't skip them:
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('link names are distinct and match visible text', async ({ page }) => {
  await page.goto('/blog');
  const results = await new AxeBuilder({ page })
    .withRules(['identical-links-same-purpose', 'label-content-name-mismatch'])
    .analyze();

  // Mismatched labels are real failures
  expect(results.violations).toEqual([]);
  // Identical names only produce "needs review": log them for a human
  for (const item of results.incomplete) {
    console.log(item.id, item.nodes.map((n) => n.target));
  }
});
  1. Open a links list yourself. Turn on NVDA and press NVDA+F7, or VoiceOver on a Mac and press VO+U, then read the links on your busiest page. If you can't tell where each one goes from the list alone, neither can your users. It takes five minutes and it's the most reliable check of the three.

If you already run axe in CI, the snippet above slots in next to your existing checks; our GitHub Actions setup guide shows where. For the other issues that make up most failing pages, see how to fix the most common WCAG violations.

Does Descriptive Link Text Help SEO Too?

Yes, and Google says so directly. Its link guidance states that "good anchor text is descriptive, reasonably concise, and relevant to the page that it's on and to the page it links to", and uses "Read more" as one of its examples of anchor text to avoid (Google Search Central, 2025). That fits with Lighthouse filing its link-text audit under SEO. Fixing a blog index's "Read more" links tells search engines what each linked post is about at the same time as it tells screen reader users. GOV.UK's content guidance lands in the same place from the other direction: avoid generic text like "click here" or "more", and don't reuse the same link text for different pages (GOV.UK).

Frequently Asked Questions

Is "Read more" link text a WCAG failure?

It depends on the context and the level. At Level A, SC 2.4.4 allows link purpose to come from programmatically determined context such as text in the same paragraph, list item or table cell, so a "Read more" inside a descriptive sentence can pass (W3C). A standalone "Read more" in an <article> card, with only a heading for context, relies on an advisory technique (H80). At Level AAA it fails SC 2.4.9 under failure F84.

Why doesn't axe-core flag "Read more" links?

link-name only checks that a link has a name, and "Read more" is a name. The rule that compares identical link names, identical-links-same-purpose, is tagged Level AAA, disabled by default, and only returns "needs review" (axe-core). In our test, a default WCAG 2.2 AA run with axe-core 4.11.1 reported no issues for any of six "Read more" variants.

Should I use aria-label or visually hidden text?

Prefer visually hidden text (W3C technique C7). It sits next to the visible words as ordinary content, so it stays in sync with them. If you use aria-label, include the visible words, ideally at the start, such as aria-label="Read more about pricing" on a link that shows "Read more". Leave them out and you fail SC 2.5.3 Label in Name for voice control users (W3C, ARIA8).

Does aria-describedby fix ambiguous links?

No. aria-describedby sets the accessible description, not the name (W3C, accname 1.2). In our test, a "Read more" link described by its card heading still had the accessible name "Read more", which is what screen readers and links lists use.

What should replace "Read more" on a blog card?

Link the post title and drop the separate link. If the whole card must be clickable, stretch the title link across the card with an absolutely positioned ::after pseudo-element rather than wrapping the card in an <a>, and keep any visible "Read more" as aria-hidden decoration (Inclusive Components, 2018).

The Bottom Line

Ambiguous link text like "Read more" is still on 15.2% of the top million home pages, and a default axe-core AA scan won't flag a single instance. In our test, that run called all six versions clean, and those are the same tags our own scanner runs by default, so it wouldn't flag them either. The axe rules that notice are AAA or experimental, so they're off unless you turn them on, and even then a person has to judge the result. Lighthouse's SEO audit catches the exact phrase, not the pattern.

The fix is almost always small. Link the heading where you can, since it's the only fix that also helps voice control users. Otherwise put the destination into the link's accessible name with visually hidden text, and keep the visible words at the start of any aria-label. Skip aria-describedby and title; they leave the name unchanged. Then open a links list on your busiest page and listen. If each link makes sense on its own, you're done. For where this sits among the rest of the data, see our web accessibility statistics.


Sources