# Pagination links that don't say which page or where they go

Source: https://easeweb.dev/learn/pagination
Topics: Menus and navigation > Pagination says which page is current and where each link goes (WCAG 1.3.1, 2.4.4)
The fix: Put the page links in a named nav, say which page is current in the code or the text, and give every page link a name in words. Either keep the numbered design, with aria-current="page" on the current page and hidden text that names each number and arrow, or replace the numbers with Previous page and Next page links around the words "Page 2 of 4".
Test with: screen reader, keyboard, speech input
References: https://www.w3.org/WAI/WCAG22/Understanding/info-and-relationships.html https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html https://www.w3.org/WAI/WCAG22/Techniques/html/H97 https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-current https://design-system.service.gov.uk/components/pagination/

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A shop's category page for mugs ends with a row of page links: an arrow back, the numbers 1 to 4, and an arrow forward. The design is tidy and familiar. On the second page the 2 sits in a dark square, so a sighted shopper sees at once that they are on the second of four pages, and that the arrows lead back to page 1 and on to page 3.

A screen reader user pressing Tab through the same row hears "‹, link", "1, link", "2, link", "3, link", "4, link", "›, link". Nothing says that 2 is the page of results being shown, or that the numbers are pages at all. The arrows are read by whatever name the screen reader or its voice has for the symbol, or not at all, depending on the reader and its punctuation setting, so the link that means Next page says nothing about where it goes. The row is not announced as navigation and doesn't appear in the screen reader's list of landmarks, so there is no way to jump to the page links at the end of a long grid of products.

## Why it fails

The page links show relationships: they are a set of links for moving through one list, and one of them is the page being shown. WCAG 1.3.1 Info and Relationships requires information shown by presentation to be available in the code, or in the text. Here the grouping is only in the layout, because the row is a `div`, which groups nothing for assistive technology; technique H97 meets 1.3.1 by putting related links in a `nav`. The current page is only in the look, because the `current` class means nothing to the browser.

The links also fail 2.4.4 Link Purpose (In Context). A link's purpose must be clear from its text, or from its text with its programmatically determined context: the sentence, paragraph, list item or table cell it sits in, or the header cell of that cell. "›" says nothing about where it goes, and "2" doesn't say it is a page of these results. The eye takes that meaning from where the row sits under the grid and from what the arrows usually mean; the markup carries neither, and the `div` around the links gives a screen reader no context to reach.

## Who is affected

Pagination sits under nearly every shop category, search results page, blog archive and forum, and it usually comes from one shared component, so one fix reaches every list on the site. It matters most on long lists, where people need to know how far through they are and how much is left.

## What automation and AI miss

A scanner finds links that all have names, "2" and "›" included, and passes them; an empty link would fail, a symbol doesn't. It can't tell that one link is drawn as current, because a class called `current` or `active` means nothing to it, and it can't judge whether a number says what it leads to. EaseWeb's audit reports a link to the page being shown, in page links named so by their class or name, that is drawn unlike the links beside it and has neither `aria-current` nor text saying it is current, and a link whose whole name is symbols with no words in the sentence, list item or cell around it. Page links outside any navigation landmark go to a person as a review lead, with their links; bare numbers such as "2" are left to that person too. Pagination written by hand, copied from a component library or generated by an AI tool needs the same check: listening to the page links on two pages is the only way to confirm they say where you are and where they go.

## Before: page links marked by a class, with symbols for arrows

```html
<h1>Mugs</h1>
…
<div class="pagination">
  <a href="/kitchen/mugs?page=1">‹</a>
  <a href="/kitchen/mugs?page=1">1</a>
  <a href="/kitchen/mugs?page=2" class="current">2</a>
  <a href="/kitchen/mugs?page=3">3</a>
  <a href="/kitchen/mugs?page=4">4</a>
  <a href="/kitchen/mugs?page=3">›</a>
</div>
```

```css
.pagination {
  display: flex;
  gap: 0.25rem;
}
.pagination a {
  min-width: 2.5rem;
  border: 1px solid #c9d0d6;
  border-radius: 0.375rem;
  text-align: center;
}
.pagination .current {
  border-color: #1d1d1f;
  background: #1d1d1f;
  color: #fff;
  font-weight: bold;
}
```

The `current` class gives the 2 its look and nothing else, and the `div` groups nothing. A screen reader reads "2, link" like its neighbours, and "‹, link" and "›, link" for the arrows.

## After: a named nav, aria-current and hidden names

```html
<h1>Mugs</h1>
…
<nav class="pagination" aria-label="Pagination">
  <ul>
    <li>
      <a href="/kitchen/mugs?page=1">
        <span aria-hidden="true">‹</span>
        <span class="visually-hidden">Previous page</span>
      </a>
    </li>
    <li>
      <a href="/kitchen/mugs?page=1">
        <span class="visually-hidden">Page </span>1
      </a>
    </li>
    <li>
      <a href="/kitchen/mugs?page=2" aria-current="page">
        <span class="visually-hidden">Page </span>2
      </a>
    </li>
    <li>
      <a href="/kitchen/mugs?page=3">
        <span class="visually-hidden">Page </span>3
      </a>
    </li>
    <li>
      <a href="/kitchen/mugs?page=4">
        <span class="visually-hidden">Page </span>4
      </a>
    </li>
    <li>
      <a href="/kitchen/mugs?page=3">
        <span aria-hidden="true">›</span>
        <span class="visually-hidden">Next page</span>
      </a>
    </li>
  </ul>
</nav>
```

```css
.pagination ul {
  display: flex;
  gap: 0.25rem;
  list-style: none;
  padding: 0;
}
.pagination a {
  min-width: 2.5rem;
  border: 1px solid #c9d0d6;
  border-radius: 0.375rem;
  text-align: center;
}
.pagination [aria-current="page"] {
  border-color: #1d1d1f;
  background: #1d1d1f;
  color: #fff;
  font-weight: bold;
}
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
```

The design is unchanged: every number and arrow looks as it was drawn. The row is now a `nav` with its own name, so a screen reader announces "Pagination, navigation" on the way in and lists it among its landmarks. `aria-current="page"` on the 2 makes screen readers say "current page" there, and the dark square now selects on the attribute, so the look and the code can't mark different links. The hidden text names the links: "Page 2", "Previous page" and "Next page". The arrows themselves are hidden with `aria-hidden`, so their symbols are not read on top of the names.

## After: Previous and Next around the page count

```html
<h1>Mugs</h1>
…
<nav class="pagination" aria-label="Pagination">
  <a href="/kitchen/mugs?page=1">
    <span aria-hidden="true">‹</span>
    Previous page
  </a>
  <p>Page 2 of 4</p>
  <a href="/kitchen/mugs?page=3">
    Next page
    <span aria-hidden="true">›</span>
  </a>
</nav>
```

```css
.pagination {
  display: flex;
  align-items: center;
  gap: 1rem;
}
.pagination a {
  padding: 0.5rem 0.75rem;
  border: 1px solid #c9d0d6;
  border-radius: 0.375rem;
}
.pagination p {
  margin: 0;
  font-weight: bold;
}
```

This changes the resting look: the numbers give way to two links that say in words where they go, and the current page is written between them as "Page 2 of 4", in text everyone reads, so there is nothing to hide and no state to mark. A screen reader reads "Pagination, navigation", "Previous page, link", "Page 2 of 4", "Next page, link"; voice control users can say "click Next page" as written. What the design gives up is the jump: someone on page 2 of 40 can't go straight to page 40, only one page at a time.

## Which option to use

Both pass 1.3.1 and 2.4.4. Keep the numbers, with `aria-current` and hidden names, when lists run long or people jump around in them, such as search results and archives: the design stays as drawn, and the look comes from the attribute. Use Previous page and Next page around the page count when lists are short, when the numbers don't fit on a phone anyway, or when you want every name in plain sight: nothing is hidden, and voice control users can say exactly what they see.

## Implementation decisions

The page links have a few more choices:

- `aria-label` instead of hidden text: `<a href="…" aria-label="Page 2">2</a>` gives the same name. Browser translation has been less reliable with an `aria-label` than with text in the page.
- The name: "Pagination" is enough, because the screen reader adds "navigation" itself; see [navigation landmarks that all sound the same](/learn/landmark-names). When the same page links appear above and below the list, give the two `nav` elements different names, or leave the top row out of a `nav`.
- The first and last page: on page 1, leave Previous page out or show it as plain text, not a link that leads to the same page or nowhere. An ellipsis between page numbers stays as text in a list item of its own, outside any link.
- Lists that change without a page load: when sorting, filtering or a page link swaps the results in place, move `aria-current` with them, and say that the list changed; see [single-page app route changes that are never announced](/learn/route-changes).
- Page titles: "Mugs, page 2 of 4" in the page's title tells screen reader users where they are as soon as the page loads, which 2.4.2 Page Titled covers in [page titles that are the same on every page](/learn/page-title).

Keep the current page visible in a Windows contrast theme. Forced colors drop the dark fill behind the 2, so it would look like the other numbers; its bold weight survives, and so would a border drawn thicker than the others'.

## Verify the fix

1. In the code or the browser's inspector, confirm the page links are in a `nav` with its own name, that the current page has `aria-current="page"`, hidden text or the page in text, that every page link's name is in words, and that the current-page look comes from the same condition.
2. Open the screen reader's landmarks list and confirm the page links are listed by their name, apart from the site's main menu.
3. Read the page links and confirm each number is read as a page, the current one as current or as "Page 2 of 4", and each arrow as Previous page or Next page.
4. Go to another page of results by a number, by Next and by the browser's Back button, and confirm both the look and the announcement have moved to the new page.
5. With voice control, say "click Next page" and confirm it follows the link to the next page.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after the shared pagination component changes.

## Limits

This is a starting pattern, not a guarantee for every list. Screen readers word `aria-current` and landmarks differently, and how they read symbols depends on the punctuation setting; test with the ones your visitors use. Lists that load more on scroll or with a Load more button need their own care, because they have no pages to mark. The breadcrumb trail at the top of the same page has a failure of its own; see [breadcrumbs that mark the current page only by its look](/learn/breadcrumbs). The site's main menu marks its current page with the same attribute; see [navigation that marks the current page only by its look](/learn/current-page).

---

Get this guide in your coding agent: MCP https://easeweb.dev/mcp (setup at https://easeweb.dev/services/developers)
