What 2.4.4 requires

The purpose of each link can be determined from the link text alone or from the link text together with its programmatically determined link context, except where the purpose of the link would be ambiguous to users in general.

Success criterion 2.4.4 Link Purpose (In Context) (Level A), quoted from Web Content Accessibility Guidelines (WCAG) 2.1, W3C Recommendation 06 May 2025. Copyright © 2020-2025 World Wide Web Consortium. Used under the W3C Document License.

Screen readers can list every link on a page, and keyboard users move from link to link. Out of their paragraph, ten links that say “Read more” are ten identical choices. Link text that names its destination works everywhere; WCAG also accepts text that becomes clear with its programmatically determined context.

Understanding 2.4.4 names that context: the same sentence, paragraph, list item or table cell as the link, a header cell of that table cell, or text tied to the link with ARIA. A heading before the link is only an advisory technique (H80). The stricter 2.4.9, at Level AAA, asks for the link text alone.

The exception is a purpose that is ambiguous to everyone, such as the doors of a game that only reveal what is behind them when opened. WCAG lists two failures: context only in content that is not related to the link (F63), and an image that is the only content of a link and has no accessible name (F89).

Common failures

Under each example: what axe-core and Rampa reported on the markup before the fix, in a run with Gemma 4 12B on 9 October 2026.

Generic text, with the context somewhere else

The sentence that explains the link is in a different paragraph, so it is not the link’s context: a screen reader’s list of links shows “Click here” and nothing else. That is WCAG failure F63. A “Read more” at the end of the paragraph or list item that names the article would pass 2.4.4.

Before, fails 2.4.4

<p>Our return policy changed on 1 March.</p>
<p><a href="returns.html">Click here</a></p>

After, passes

<p>Our return policy changed on 1 March.
  <a href="returns.html">Read the new return policy</a>.</p>
axe-core

Passes: link-name finds a name.

Rampa
✗ html > body > main > p:nth-of-type(2) > a
  The link text "Click here" does not tell where the link goes, and nothing around it does.
  Evidence: "Click here"
  Patch:
    - <a href="returns.html">Click here</a>
    + <a href="returns.html">Returns and refunds</a>
  confidence high · 1/1 runs · evidence verified · id 1af8a73e209d

The suggested text is the title of the page at returns.html, which Rampa read before judging.

An image link with no name

The logo links home, but the image has no alt, so the link has no name at all. That is WCAG failure F89, and here rules decide: axe-core fails it, and Rampa reports that without asking a model.

Before, fails 2.4.4

<a href="/"><img src="/logo.svg"></a>

After, passes

<a href="/"><img src="/logo.svg" alt="Aurora Roasters home"></a>
axe-core

Fails: link-name finds no name, and image-alt finds no alt.

Rampa
✗ html > body > header > a
  Links must have discernible text
  high · rule link-name · id 1eed5c5c986f

The same run reports the image under 1.1.1, from image-alt.

Text that does not match the destination

The link promises subscription prices and leads to a blog post about a new roastery. Before judging, Rampa read the page at the address, so the model could compare its title with what the link says.

Before, fails 2.4.4

<p>Fresh coffee every two weeks, delivered to your door.
  <a href="blog/new-roastery.html">See subscription prices</a>.</p>

After, passes

<p>Fresh coffee every two weeks, delivered to your door.
  <a href="subscriptions.html">See subscription prices</a>.</p>
axe-core

Passes: link-name finds a name.

Rampa
✗ html > body > main > p:nth-of-type(3) > a
  The link text "See subscription prices" does not match where the link leads: a page titled "Our new roastery opens in March | Aurora Roasters".
  Evidence: "See subscription prices"
  Patch:
    - <a href="blog/new-roastery.html">See subscription prices</a>
    + <a href="blog/new-roastery.html">Read about our new roastery opening in March</a>
  confidence high · 1/1 runs · evidence verified · id 461d6527612c

Here the address was what was wrong, and the patch rewrites the text to match it instead. A patch is a proposal for a person to review.

The same text for two different files

Both price lists are a “Download” link, in list items that hold nothing else, under one heading. A screen reader’s list of links shows two identical choices. Rampa counts the links that share a text and the places they lead to, and tells the model whether anything around this one sets it apart.

Before, fails 2.4.4

<h2>Price lists for cafés</h2>
<ul>
  <li><a href="prices-retail.csv">Download</a></li>
  <li><a href="prices-wholesale.csv">Download</a></li>
</ul>

After, passes

<h2>Price lists for cafés</h2>
<ul>
  <li><a href="prices-retail.csv">Retail prices (CSV)</a></li>
  <li><a href="prices-wholesale.csv">Wholesale prices (CSV)</a></li>
</ul>
axe-core

Passes: link-name finds a name for each link.

Rampa
✗ html > body > main > ul:nth-of-type(1) > li:nth-of-type(1) > a
  The link text "Download" is shared by 2 links that lead to 2 different places, and nothing around this one tells it apart.
  Evidence: "Download"
  Patch:
    - <a href="prices-retail.csv">Download</a>
    + <a href="prices-retail.csv">Download retail prices CSV</a>
  confidence high · 1/1 runs · evidence verified · id ec4bd01e9793
✗ html > body > main > ul:nth-of-type(1) > li:nth-of-type(2) > a
  The link text "Download" is shared by 2 links that lead to 2 different places, and nothing around this one tells it apart.
  Evidence: "Download"
  Patch:
    - <a href="prices-wholesale.csv">Download</a>
    + <a href="prices-wholesale.csv">Download wholesale prices CSV</a>
  confidence high · 1/1 runs · evidence verified · id 6cc094186f49

What axe-core checks

axe-core 4.14.0 maps two rules to 2.4.4, both also mapped to 4.1.2: link-name fails a link with no name, and area-alt an image map area with no alternative. Rampa reports their failures as they are, and a link that link-name failed never goes to the model.

axe-core 4.14.0 rules for 2.4.4
RuleWhat it checksMapped toIn a Rampa run
link-name, rule page at Deque UniversityLinks must have discernible text2.4.4, 4.1.2runs
area-alt, rule page at Deque UniversityActive <area> elements must have alternative text2.4.4, 4.1.2runs

Neither reads the name. “Click here”, “Read more”, two “Download” links to different files and a link that names the wrong page all pass. axe-core’s identical-links-same-purpose compares links that share a name, but it is mapped to 2.4.9 (Level AAA), outside the levels Rampa runs.

Each rule links to its page at Deque University, which makes axe-core.

What Rampa judges, and how

What goes to the model

Every visible link with a name that link-name did not fail. A link whose only content is an image is left to 1.1.1, through the image’s alt, unless its name comes from aria-label or aria-labelledby.

What the model sees

  • The link text, its address and its start tag.
  • Its programmatically determined context: the sentence, paragraph or list item it sits in, with the items it is nested in, its table cell and that cell’s header cells, and its description from aria-describedby. A short div or span around a link counts as its sentence; a block that holds nothing but links is no context.
  • The heading the link sits under, and the landmark around it, such as navigation.
  • Where the link leads: for a #part link, the text it points to; for other links, what Rampa read at the address before judging, which is a page’s title, first h1, meta description and the part a #fragment points to, or a file’s type and size.
  • The links that share its text: how many there are, how many different places they lead to, and whether this link’s context tells it apart from the ones that lead elsewhere.
  • The page language, for the suggestion.

What it must answer

First, what the text and its context promise and where the content says the link leads; then a verdict, the link text copied as evidence, the problem (generic, an address or file name, a mismatch with where it leads, or ambiguous among links with the same text), a link text that states the purpose on its own in under 80 characters, and a confidence.

What drops a claim

  • The evidence is not the link’s current text.
  • A pass names a problem, or a fail names none.
  • A fail has no suggested text, one over 160 characters, or one equal to the current text.
  • A mismatch is claimed while the destination is unknown: no #part, no address, nothing read there.
  • Ambiguity is claimed while no other link with the same text leads elsewhere from the same context.

The patch

The suggested text replaces the link text, or its aria-label when that is where the name comes from.

For every criterion

  • Page content reaches the model marked as data, and the prompt tells it to ignore any instruction inside.
  • Answers are cached by a hash of the prompt, the image, the model and its settings, so the same input never calls a model twice.
  • --runs 3 asks three times and keeps the majority; when runs disagree, confidence drops. Findings below --min-confidence (medium by default) are hidden, and --verbose lists them.
  • Claims dropped by verification are counted in every report, never shown as findings.

How Rampa was measured on it

W3C ACT test cases for 2.4.4: precision, recall and F1, Gemma 4 12B on a local GPU
SetTestsCasesaxe-core, precision / recallRampa, precision / recall / F1
2.4.4, link has a name (ACT c487ae)syntax281.00 / 1.001.00 / 1.00 / 1.00
2.4.4, link in context is descriptive (ACT 5effbb)meaning18not defined, nothing flagged / 0.000.86 / 1.00 / 0.92
2.4.4, links with the same name serve the same purpose (ACT fd3a94)meaning24not defined, nothing flagged / 0.000.58 / 0.88 / 0.70

Corrupted pairs: Rampa told 10 of 11 apart (link text made generic) and 10 of 10 apart (link pointed elsewhere), axe-core 0. A pair is a passing test page and a copy broken on purpose; a checker that gives both the same verdict is not judging.

Run on 9 October 2026: axe-core 4.14.0, Gemma 4 12B on a local GPU, reasoning off, one run, ACT test cases a9a1483e. Small samples and one run: read the numbers as a working pipeline, not as a result.

Read with these caveats

  • fd3a94 is the weakest set. It asks whether links that share a name lead to equivalent places, and ACT accepts copies of a page and links “ambiguous to users in general” that Rampa reports.
  • The evaluation follows same-origin links, because the outcomes of fd3a94 depend on where its links lead, an instant redirect or a copy of a page, on the same W3C host as the test pages. Third-party sites the test pages link to are never contacted.
  • The pairs break the passing examples of c487ae and 5effbb: one gives every link whose text stands alone the text “Check it out”, the other points every link at a paragraph about opening hours added to the page. Neither uses words from the prompt.
  • The prompt and the context for 2.4.4 were adjusted after reading its errors on these cases, without copying the test pages into the prompt. They need fresh pages before the numbers mean more than “the pipeline works”.

Method and error analysis in the README

Limitations

  • In the run recorded for this page, Gemma failed one of two “Read more” links whose list items name the news, with high confidence, in each language. By WCAG technique H77 those pass 2.4.4: they are false positives.
  • Destinations are read only when the judgment runs, not with --no-llm: at most 40 addresses per page, 4 at a time, 8 seconds each and the first 512 KB of a page, with no cookies or credentials and without running the page’s scripts. A page that builds its content in the browser shows only what its HTML says.
  • By default only addresses on the page’s own origin, or local files beside it, are read. --follow-links all adds other sites, never a private address unless the page is on one, and --follow-links none reads nothing. A broken link, a sign-in wall or a redirect to the home page reads as nothing known, and is never a finding.
  • What Rampa reads at a destination goes into the prompt, so a hosted model provider sees the titles and descriptions of the pages your page links to. Use --follow-links none, or a local model, when that matters.
  • The ACT exception for links “ambiguous to users in general” is not detected: two links with the same text that lead to different places, with nothing around them for anyone, are reported as ambiguous.
  • Two pages count as one place when their title, h1 and description are identical and only their path differs. A scripted link without an href has no known destination, so it never joins the comparison.
  • The model also sees the heading the link sits under. WCAG lists that only as an advisory technique (H80), and the ACT definition of link context leaves it out, so a pass that rests on the heading deserves a person’s look.

Questions about 2.4.4

Does “Read more” fail WCAG 2.4.4?

Not always. When the sentence, paragraph, list item or table cell around it says what it leads to, “Read more” passes 2.4.4; it fails the stricter 2.4.9 (Level AAA), which asks for the link text alone. A “Read more” with nothing around it fails 2.4.4. In the run recorded for this page, Rampa wrongly failed one of two “Read more” links whose list items named the news.

Does Rampa follow the link?

It reads where the link leads before judging, without running scripts: a page’s title, main heading and description, or a file’s type and size. By default it reads only links on the same site, or local files next to the page; --follow-links all adds other sites and --follow-links none turns it off. The destination can reveal a mismatch, but it never makes an unclear text pass: people do not see it before they follow the link.

Are two links with the same text a failure?

Not when they lead to the same place, or when the context of each tells them apart. Rampa reports a shared text as ambiguous only when the links lead to different places and nothing around this one sets it apart. ACT rule fd3a94 tests this, and it is where Rampa is weakest.

Does a heading count as the link’s context?

Not as a sufficient technique. WCAG lists the preceding heading as an advisory technique (H80), and the ACT rules leave headings out of link context. Rampa still shows the model the heading a link sits under, so a pass that rests only on the heading deserves a person’s look.

Can axe-core check link purpose?

It checks that a link has a name (link-name) and that an image map area has alternative text (area-alt). A link that says “Click here” passes both.

Check your pages

Rampa is not on npm yet. Clone it and run it from source with Node.js 22.12 or newer and Chrome or Edge. --criteria 2.4.4 judges only this criterion; leave it out to run the eight that run by default.

With Ollama running, Rampa picks a local model by itself; node dist/cli.mjs doctor says what is missing.

git clone https://github.com/guilhermebsantiago/rampa-cli.git
cd rampa-cli
pnpm install && pnpm build
node dist/cli.mjs check https://example.com --criteria 2.4.4

Checked against rampa-cli (commit 0459809) and the W3C sources on 9 October 2026.