Pagination
StableSplits a long set of results into pages and lets people move between them — predictably, shareably, and with a sense of how much there is.
Anatomy
- 1PreviousGoes back one page. Collapses to its arrow on small screens.
- 2Page linkA real link to a page, ideally encoded in the URL.
- 3Current pageOutlined, and announced as the current page.
- 4EllipsisStands in for skipped pages.
- 5NextGoes forward one page.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/pagination.jsonUsage
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="?page=1" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="?page=2" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="?page=3" />
</PaginationItem>
</PaginationContent>
</Pagination>Pages are real links. Put the page number in the URL (?page=3) so results are shareable, bookmarkable, restorable with the back button and indexable by search engines.
Examples
Default
The current page uses isActive, which switches it to the outline style and sets aria-current="page". Previous and Next collapse to their arrows below the sm breakpoint.
Controlled with a page range
For many pages, show the first page, the last page, and the current page with its neighbours; replace gaps with PaginationEllipsis. The number of items never changes as people page through, so controls don't shift under the pointer.
Page 7 of 20
Compact
When space is tight or the exact page matters less than direction, show the position as text between two icon links. Give each icon link an aria-label.
Table footer
Data tables pair pagination with a page-size control and a range summary (26–50 of 237). Reset to page one when the page size changes.
Guidelines
When to use
- For result sets people search and filter, and need to return to: tables, admin lists, search results, archives.
- When the position in a set matters, or people need to reach the end without loading everything before it.
When not to use
- For feeds meant to be browsed rather than searched — infinite scroll or a Load more button fits better.
- For fewer than about 25 items — show them all on one page.
- For steps in a process — use a stepper; pages imply equal, independent chunks.
Pagination, load more, or infinite scroll
Behavior
Keep the control in the same place on every page — below the results, and optionally above long ones. Scroll back to the top of the results after changing pages, and keep filters and sorting when the page changes. Show the total when you know it; it helps people decide whether to refine their search.
Accessibility
- Landmark.
Paginationrenders a<nav>labelled Pagination — in the language set by LocaleProvider. If a page has two (above and below results), label them distinctly, such as Pagination, top and Pagination, bottom. - Current page.
isActivesetsaria-current="page", so the current page is announced, not just styled. - Direction links are labelled Go to previous page and Go to next page, even when only their arrows are visible.
- Ellipses are hidden from assistive technology; the page numbers around them carry the meaning.
- Disabled ends. On the first and last page, set
aria-disabledon Previous or Next and keep them in place, so the layout doesn't shift.
| Key | Behavior |
|---|---|
Tab | Moves focus through the page links in order. |
Enter | Follows the focused link. |
API reference
Pagination
The <nav> landmark. Accepts all <nav> props.
PaginationContent and PaginationItem
PaginationContent is the <ul> that lays out items; PaginationItem is each <li>.
PaginationLink
Renders an <a> styled as a prfct Button.
PaginationPrevious and PaginationNext
PaginationEllipsis
A presentational ⋯ that stands in for skipped pages.