Pagination
Page navigation for lists and tables. Fully controlled: it renders the state you pass it.
It doesn't hold state
page and onPageChange are both required. The component never moves on its own, which is what lets the same page number live in a URL query param or a data-fetching hook rather than being trapped in here.
How it pages
The first and last page are always there. Between them, page numbers come in fixed blocks and the highlight moves across them, so the numbers only change when you step past the edge of the block, never on every click. It stays the same width the whole way through, and an ellipsis always hides at least two pages.
Siblings
Higher values show a bigger block of pages at the cost of width. Below, the same page in a 24-page set with one sibling and then two.
In a table footer
Pair it with a count so people know the size of what they're paging through, not just where they are in it.
Showing 1–20 of 482
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| total | number | Total number of pages, not total rows. Divide by your page size first. | |
| page | number | Current page, 1-based. | |
| onPageChange | (page: number) => void | Fires with the requested page. | |
| siblings | number | 1 | Sets how many page numbers sit between the ellipses, in blocks of siblings × 2 + 1. The control is always siblings × 2 + 5 slots wide. |
Why it works this way
Why
- Long ranges collapse with ellipses, and the numbers between come in fixed blocks, so the highlight moves instead of the layout jumping.
Don't
- Don't use it for an infinite feed. Load more, or scroll, fits that better.