Pagination
Pagination splits a long list into pages someone moves through one at a time. Numbered pages, a load more button and infinite scroll all do that job, and choosing between them comes down to whether anyone needs to find their way back.
<nav aria-label="Pagination">Also called Pager, Paging, Load more, Infinite scroll.
Pagination is three different components under one name. Numbered pages, a load more button and infinite scroll all split a long list into batches, and they answer three different questions about what happens after someone leaves the page.
Anatomy of pagination

- 1Result range. “Showing 41 to 60 of 312”. Three numbers telling someone whether to keep paging or add a filter.
- 2Previous. Disabled in place on page 1 rather than hidden, so the row doesn't reflow under the reader's cursor.
- 3Page link. A real <a href> so middle-click and open-in-new-tab work, named with aria-label="Page 4" rather than left as a bare digit.
- 4Current page. Marked visually and with aria-current="page". This is the orientation in a control where everything else looks alike.
- 5Truncation. The gap standing in for pages 5 to 15. Decoration, so aria-hidden="true".
- 6Next. Keeps its word alongside the chevron down to about 480px, where the label can be dropped and the aria-label kept.
The current page marker is the part doing the most work. It answers "where am I" in a control where every other item looks the same, so it needs a visual treatment strong enough to spot without reading, and aria-current="page" so the same answer exists in code.
Here it is working. Move between pages and watch the range line, the current marker and the disabled ends update together.
Showing 41 to 60 of 312
Move between pages. The range line, the current page marker and the disabled ends all update together.
When to use pagination
Use pagination when
- The list is long enough that loading all of it at once is slow
- Results are ranked, so position on the list carries meaning
- Someone will want to come back to the same place later, or send it to a colleague
- There's a footer under the list that has to stay reachable
Reach for something else when
- The whole list fits in a screen or two. Under roughly 25 items a pager adds a control and hides nothing. Use one scrolling list instead.
- People are hunting for one specific record rather than browsing a ranked set. Use search and filters instead.
- It's a handful of peer views rather than a long list of the same kind of thing. Use tabs instead.
Variants
Showing 41 to 60 of 312
Numbered for jumping, previous and next when the total is unknown, load more to keep the reading position, and a range line where the total is the point.
Numbered. Previous, a run of page links with the current one marked, a truncation gap, the last page, then next. The most information for the most cost in width, and the only variant that lets someone jump.
Previous and next only. Two controls, no numbers. Right when the total is unknown or expensive to count, which is most cursor-based APIs, and right on narrow screens where a row of numbers has to shrink below a usable target size.
Load more. One button that appends the next batch below what's already there. Keeps the reading position, keeps the footer reachable, and gives you a natural place to say how many are coming.
With a result range. "Showing 21 to 40 of 312" beside a rows-per-page control. Belongs under a table, where people need the total as much as the navigation.
States
| State | What changes | Watch out for |
|---|---|---|
| Current | Filled or outlined, aria-current="page" set | Should not look clickable, and should not be a dead link either |
| Default | Plain number, full colour | Still needs 4.5:1 text contrast (WCAG 1.4.3), even at this size |
| Hover | Background or underline appears | The whole target reacts, not just the digit |
| Focus | A visible ring on the control | Has to clear the neighbouring numbers, which sit close together |
| Disabled | Previous on page 1, Next on the last page | Keep it in place rather than hiding it, or the row shifts as you move through |
| Loading | Spinner in the load more button, label unchanged | Keep the button's width fixed so it doesn't jump mid-click |
Disabled ends are worth doing properly. Removing "Previous" on page 1 shifts every other control one position left, so the button under the reader's cursor changes between page 1 and page 2. Leave it in place and disable it.
The current page marker gets the opposite treatment from everything around it. If the other numbers are plain text on hover backgrounds, the current one should be filled. If the others are ghost buttons, the current one should be outlined. Whatever the pairing, it should not read as clickable, because pressing it does nothing and that is a small, repeated disappointment.
How pagination behaves
The URL carries the page. A query parameter or a route segment, updated on every change. Back, forward, bookmark, refresh and a pasted link all follow from that one decision.
Changing page moves focus. Focus goes to the list heading or the first result, not back to the top of the document and not left on a button that's now in a different position. Without it, a keyboard user presses "Next" and their focus stays in the pager while the entire page under it changed silently.
A page change announces itself. A live region saying "Page 3 of 16, showing 41 to 60" covers the screen reader user who just lost every visual cue at once.
Load more appends, it doesn't replace. What's already been read stays where it was, and the scroll position holds. Move focus to the first new item afterwards, or announce how many arrived.
Filters reset to page 1. A new filter makes a new list, and page 7 of the old one means nothing. Say so with the result range rather than silently jumping.
Page size persists. Someone who sets 100 rows per page expects 100 next time. Store it, and keep it out of the URL if it's a personal preference rather than part of what's being shared.
Pagination accessibility
Keyboard
| Key | What happens |
|---|---|
Tab | Moves through the controls in order: previous, each page link, next |
Enter | Follows the focused page link |
Space | Activates a load more button, since that's a <button> rather than a link |
Tab after a change | Should land in the new content, because focus was moved there deliberately |
The rest
- Wrap it in
<nav>with a label.<nav aria-label="Pagination">. If there's a pager above and below the same list, the two labels have to differ, or a screen reader announces two identical landmarks with no way to tell them apart. aria-current="page"on the current one. The visual marker is invisible to a screen reader without it.- Name every number. A bare "4" announces as "4, link".
aria-label="Page 4"costs one attribute and makes the control readable. - Hide the truncation gap. The ellipsis between page 3 and page 16 is decoration, so
aria-hidden="true". A screen reader reading "page 3, page 16" needs no dots between them. - Page links are links.
<a href>with a real address, so middle-click, open in a new tab and copy link address all work. Load more is a<button>, because it changes the page rather than going to a new one. - Target size. 24 by 24 CSS pixels at Level AA (WCAG 2.5.8). Page numbers are the control most often shipped under that, because a single digit looks fine in a 20px box.
- Contrast. Chevrons in previous and next carry meaning on their own once the words are hidden on narrow screens, so they need 3:1 (WCAG 1.4.11).
What pagination should say
Give the total when you have it. "Showing 21 to 40 of 312" tells someone whether to keep going, refine their search, or stop. Three numbers, one line.
Say what load more will load. "Load 20 more" beats "Load more". The reader can decide whether it's worth the wait before they commit to it.
Keep the words on previous and next. Chevrons alone are ambiguous in a right-to-left layout and small everywhere. Hide the words below about 480px if you have to, and keep the aria-label.
Don't invent a total you don't have. "Page 1 of many" and "1 to 20 of 1000+" both read as an apology. Previous and next only, with no count, is the honest version of that list.
Common pagination mistakes
Showing 41 to 60 of 312
The current page is marked, so the control answers where am I as well as where can I go. aria-current says the same thing in code.
Four identical numbers and no total. Nothing says which page this is, so the pager has stopped being orientation.
The one that generates support tickets is a pager that never touches the URL. Someone finds a result on page 6, sends the link to a colleague, and the colleague opens page 1 and says they can't see it.
1. The URL never changes. Back, bookmark, refresh and share all break at once. Everything else on this list is smaller than this.
2. Focus left behind after a page change. The content changes, focus doesn't move, and a keyboard user's next Tab continues from a control that's now sitting under a different list.
3. Infinite scroll above a footer. Contact details, terms and the site map become unreachable, because new items keep arriving faster than the reader can get past them.
4. Previous and Next disappearing at the ends. The row reflows and the control under the cursor changes between page 1 and page 2. Disable them in place instead.
5. Page numbers under 24px. A digit in a tight box looks balanced in the design and misses on a phone, especially for a hand that isn't steady.
6. The current page styled like every other number. Nothing on screen says where the reader is, so the pager stops being orientation and becomes seven identical buttons.
7. Losing the scroll position on back. A reader who opens a result, presses back, and lands at the top of page 1 has to redo the whole journey. Restore both the loaded batches and the scroll offset.
How to build pagination
<nav aria-label="Pagination">
<ul>
<li>
<a href="?page=2" aria-label="Go to previous page" rel="prev">Previous</a>
</li>
<li><a href="?page=1" aria-label="Page 1">1</a></li>
<li><a href="?page=2" aria-label="Page 2">2</a></li>
<li><a href="?page=3" aria-label="Page 3" aria-current="page">3</a></li>
<li aria-hidden="true">…</li>
<li><a href="?page=16" aria-label="Page 16">16</a></li>
<li>
<a href="?page=4" aria-label="Go to next page" rel="next">Next</a>
</li>
</ul>
</nav>
A list inside a labelled <nav> is the whole pattern. The list gives a screen reader a count, so it can say "list of 7 items" before reading them, and the label distinguishes this nav from the site navigation above it.
Google said in 2019 that it had stopped using rel="next" and rel="prev" as indexing signals. They're still valid HTML and other tools read them, so they cost nothing to include. Just don't add them expecting a ranking effect.
For load more, swap the link for a button and keep the range line updated:
<p role="status">Showing 1 to 20 of 312</p>
<button type="button">Load 20 more</button>
role="status" makes that line a live region, so the count is announced when it changes rather than sitting there as text nobody is told about.
Edge cases
One page of results. Hide the pager. A row of controls that can only ever say "1" is furniture.
No results at all. No pager, and an empty state that says whether the list is empty or the filters matched nothing.
A page number past the end. People edit URLs, and old links outlive their data. Redirect to the last page or show a clear message, never a blank list under a working pager.
Rows changing while someone reads. Offset pagination on a list that's actively being added to will show the same row twice or skip one, because the offset moves under the reader. Cursor pagination fixes it, at the cost of losing page numbers, which is why so many admin tools ship previous and next only.
Very deep pages. Page 400 is slow to query and almost never wanted. Cap the numbered range and lean on search and filters past that point.
Print. Printing page 3 of a list prints 20 rows. If the document is meant to be complete, offer a print or export view that ignores paging entirely.
Pagination in real products
Google search has run the experiment in public. Continuous scrolling arrived on mobile results in 2021 and on desktop the year after, then was rolled back in 2024 to a "More results" button on mobile and numbered pages on desktop. The stated reason was that people wanted control over loading more.
Amazon's search results put the count above the list and numbered pages below it, with previous and next at each end. The count is what tells someone to add a filter rather than page through 30 screens.
GitHub's issue lists use previous and next with the page in the URL, so a link to a filtered list on page 2 opens on page 2. The filter and the page travel together in the query string, which is what makes those links worth sending.
Common questions
- Numbered pages, load more, or infinite scroll?
- Numbered pages when position matters and people return: search results, invoices, anything ranked or archived. A load more button when the list is browsed in one sitting and the footer still has to be reachable, like a product grid. Infinite scroll only for a feed with no end and no footer, where nobody will ever look for the thing they saw three minutes ago. Infinite scroll is the one that costs the most to build well, because you have to restore both the loaded items and the scroll position when someone presses back.
- How many items per page?
- Most product lists land between 20 and 50. Pick the number by how much fits on a laptop screen without scrolling twice, then check it against how slow the query gets. If people work in the list daily, give them a rows-per-page control and remember the choice, because a power user's answer and a first-time visitor's answer are rarely the same.
- Does the URL have to change when the page changes?
- Yes, for anything worth returning to. Put the page in a query parameter or a route segment so the browser back button, a bookmark and a pasted link all land on the right page. This is the single most common thing missing from a client-side pager, and it turns every shared link into page 1.
- Should the pagination sit above the list, below it, or both?
- Below is the default, because that's where someone arrives when they finish reading. Add a second set above for long tables, where scrolling back to the bottom to reach page 4 is its own chore. If you show two, give each nav a different accessible name, otherwise a screen reader announces two identical landmarks.
- Is infinite scroll bad for accessibility?
- It's harder to get right, not impossible. Three things break by default: the footer becomes unreachable because new content keeps arriving above it, keyboard users have no way to trigger the next batch, and screen readers get no announcement that anything loaded. A load more button solves all three at once, which is why it's the safer default for anything that isn't an endless feed.
Last reviewed 30 JUL 2026
