Browse the docs
Pagination
Page navigation, with the middle collapsed to an ellipsis once the list gets long.
page 4 of 12
Props
| Prop | Values | Default | Description |
|---|---|---|---|
page | number | — | Current page, 1-based. |
total | number | — | Total number of pages. Renders nothing when it is 1 or less. |
siblings | number | 1 | Pages shown either side of the current one. |
hrefFor | (page) => string | — | Renders links. |
onPageChange | (page) => void | — | Renders buttons. |
label | string | "Pagination" | Accessible name for the <nav>. |
className | string | "" | Appended after the component's own classes. |
Common patterns
// Links — the better default
<Pagination page={page} total={12} hrefFor={(n) => `/posts?page=${n}`} />
// Buttons, when the list is filtered client-side
const [page, setPage] = useState(1)
<Pagination page={page} total={pageCount} onPageChange={setPage} />
// Wider window on a desktop-only table
<Pagination page={page} total={40} siblings={2} hrefFor={hrefFor} />Notes
Prefer hrefFor. Links are shareable, they survive a page reload, they work
before hydration, and the browser gives you open-in-new-tab for free. Reach
for onPageChange only when the pages exist solely in memory.
Pass both and hrefFor wins — the component renders links and ignores the
callback.
The current page carries aria-current="page", and the arrows have visually
hidden labels, so the control announces sensibly without a title attribute.
The ellipsis is aria-hidden, because "…" read aloud between two numbers is
noise. If you need the jump-to-page affordance that some designs put there,
add a Select beside the nav instead.