Accessible Pagination Patterns
Every pattern from classic page numbers to infinite scroll
Classic Numbered Pagination
The most common pagination pattern. The key ingredients: wrap in a <nav> landmark, use proper list structure, and indicate the current page with aria-current.
<nav> Landmark
Wrapping pagination in <nav aria-label="Pagination"> creates a landmark that screen reader users can jump to directly.
aria-current='page'
This attribute tells screen readers which page is currently active. Without it, users only know visually which page is selected.
Descriptive Labels
Use aria-label="Go to page 3" on buttons. Generic "3" labels don't explain what the button does.
Try It Yourself
❌ VoiceOver: "1, 2, 3, 10" — no context that these are page numbers!
Don't: Divs with Click Handlers
<!-- ❌ INACCESSIBLE -->
<div class="pagination">
<div onclick="prevPage()">‹</div>
<div onclick="goToPage(1)" class="active">1</div>
<div onclick="goToPage(2)">2</div>
<div onclick="goToPage(3)">3</div>
<div onclick="nextPage()">›</div>
</div>
<!-- Problems:
- No keyboard access (divs aren't focusable)
- No landmark for navigation
- No indication of current page
- Screen reader can't understand structure -->Do: Semantic Navigation
<!-- ✅ ACCESSIBLE -->
<nav aria-label="Pagination">
<ul>
<li>
<button aria-label="Previous page">‹</button>
</li>
<li>
<button aria-current="page" aria-label="Page 1, current">
1
</button>
</li>
<li>
<button aria-label="Go to page 2">2</button>
</li>
<li>
<button aria-label="Next page">›</button>
</li>
</ul>
</nav>aria-hidden="true" to hide it from screen readers—it adds no useful information.Understanding the Pagination Pattern
Pagination splits a long list into pages and gives the user a way to move between them: numbered links, Previous and Next, a Load More button, or automatic loading as the user scrolls. The controls are simple. The accessibility work is in three things the controls do not show on their own: telling the user where they are, telling them what changed, and putting keyboard focus somewhere sensible afterwards.
The demo above builds numbered pagination as a <nav>landmark named "Pagination" containing a list of buttons, marks the current page with aria-current="page", gives every control a full name such as "Go to page 3", hides the decorative ellipsis, and announces "Page 3 of 10" through a live region. The Load More section shows the other half of the pattern: after new items arrive, focus moves to the first of them so a keyboard user lands on the new content instead of at the button they just pressed.
WCAG 2.2 Success Criteria Involved
| Success criterion | Level | What it requires here |
|---|---|---|
| 1.3.1 Info and Relationships | A | A named navigation landmark and a real list expose the structure that the visual row of numbers implies. |
| 2.4.4 Link Purpose (In Context) | A | Each control's name says what it does: 'Go to page 3', 'Previous page', not a bare number or an arrow glyph. |
| 1.4.1 Use of Color | A | The current page is identified by aria-current and a visual cue beyond colour, such as weight or a border. |
| 4.1.3 Status Messages | AA | When the page changes without a reload, or more items load, the change is announced through a live region because focus did not move to it. |
| 2.4.3 Focus Order | A | After Load More, focus moves to the first new item; after a page change, it stays on the control or moves to the results heading. |
| 2.5.8 Target Size (Minimum) | AA | Page-number targets packed tightly together need 24 by 24 CSS pixels each, or spacing that keeps 24 pixel circles from overlapping. |
| 2.4.1 Bypass Blocks | A | The landmark lets screen reader users jump straight to the pagination instead of reading through every result. |
| 3.2.3 Consistent Navigation | AA | The same pagination control appears in the same place on every listing page across the site. |
Expected Keyboard Interaction
| Key | Expected result |
|---|---|
| Tab | Moves through Previous, each visible page number, and Next in reading order. Disabled Previous and Next remain in the page but are skipped. |
| Shift + Tab | Moves backwards through the same controls. |
| Enter | Follows a page link, or activates a page button, and triggers the page-change announcement. |
| Space | Activates a page control built as a button; links do not respond to Space. |
| Enter or Space on Load More | Loads the next batch, announces the count, and moves focus to the first new item. |
The Failures We See Most Often
- Divs and spans with click handlers. The numbers look like links but are not focusable, have no role, and cannot be activated from the keyboard. Use anchors when a page has a URL and buttons when it does not.
- Current page shown by colour only. A filled background and nothing else. Without aria-current a screen reader hears a list of identical numbers, and a user who cannot distinguish the colour sees the same.
- Silent updates. In a single-page app the results swap with no announcement, or infinite scroll keeps loading without telling anyone, and the footer becomes unreachable. Announce the change, and offer a Load More or View All alternative to endless scrolling.
The accessible pagination guide covers the choice between links and buttons, URL and title updates for each page, how to handle very long page ranges, and the trade offs between numbered pages, Load More, and infinite scroll for different kinds of content.