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.
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).
From the W3C
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-namefinds 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 1af8a73e209dThe 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-namefinds no name, andimage-altfinds no alt.- Rampa
✗ html > body > header > a Links must have discernible text high · rule link-name · id 1eed5c5c986fThe 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-namefinds 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 461d6527612cHere 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-namefinds 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.
| Rule | What it checks | Mapped to | In a Rampa run |
|---|---|---|---|
link-name, rule page at Deque University | Links must have discernible text | 2.4.4, 4.1.2 | runs |
area-alt, rule page at Deque University | Active <area> elements must have alternative text | 2.4.4, 4.1.2 | runs |
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 shortdivorspanaround 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
#partlink, the text it points to; for other links, what Rampa read at the address before judging, which is a page’s title, firsth1, meta description and the part a#fragmentpoints 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 3asks three times and keeps the majority; when runs disagree, confidence drops. Findings below--min-confidence(medium by default) are hidden, and--verboselists them.- Claims dropped by verification are counted in every report, never shown as findings.
How Rampa was measured on it
| Set | Tests | Cases | axe-core, precision / recall | Rampa, precision / recall / F1 |
|---|---|---|---|---|
| 2.4.4, link has a name (ACT c487ae) | syntax | 28 | 1.00 / 1.00 | 1.00 / 1.00 / 1.00 |
| 2.4.4, link in context is descriptive (ACT 5effbb) | meaning | 18 | not defined, nothing flagged / 0.00 | 0.86 / 1.00 / 0.92 |
| 2.4.4, links with the same name serve the same purpose (ACT fd3a94) | meaning | 24 | not defined, nothing flagged / 0.00 | 0.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”.
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 alladds other sites, never a private address unless the page is on one, and--follow-links nonereads 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,
h1and description are identical and only their path differs. A scripted link without anhrefhas 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.4Checked against rampa-cli (commit 0459809) and the W3C sources on 9 October 2026.