Pagination

A pager for a table, a card grid, or any list: numbered pages, prev and next, a row count, and an optional rows-per-page select.

Installation

$
npx shadcn@latest add https://ui.mattdowney.com/r/pagination.json

Usage

import { Pagination } from "@/components/ui/pagination"

Examples

Default

Page numbers with the current one filled. Pages either side of it stay visible and the rest collapse to an ellipsis, so the row is the same length on page 2 as on page 40 and nothing beside it shifts as you move.

Under a table

The count and the rows-per-page control take the table's left edge, the pager takes its right. The select needs no visible label: it reads "10 per page" on its own. Changing the size sends you back to page 1, since page 9 of 47 rows stops existing at 25 per page.

OrderCustomerTotal
ORD-4820Alex Rivera$180
ORD-4821Dana Cole$217
ORD-4822Sam Okafor$254
ORD-4823Priya Nair$291
ORD-4824Alex Rivera$328

Under anything else

Nothing about this is table-shaped. With no count and no page size beside it the pager is the whole row, so it centers under the grid it belongs to.

Sam OkaforORD-4826 · $402
Priya NairORD-4827 · $439
Alex RiveraORD-4828 · $476
Dana ColeORD-4829 · $513
Sam OkaforORD-4830 · $550
Priya NairORD-4831 · $587

Paging by URL

Return the element each page should render as and the pager draws links instead of buttons, which is what a Server Component needs: it cannot hand a click handler to a button, but it can build a link. The two bounds stay buttons, since a link has no disabled state to give them.

Siblings

How many pages stay visible either side of the current one: 0, 1, and 2. Every value keeps its own row length fixed, so this is a density choice, not a behavior one.

API Reference

PropTypeDefaultDescription
page*numberNoneThe current page, 1-based.
pageCount*numberNoneHow many pages there are in total.
onPageChange(page: number) => voidNoneCalled with the page that was clicked. Omit it for link paging (renderPage); the pager only builds a click handler when it was given one.
siblingsnumber1How many pages to show either side of the current one. The first and last page are always shown, so the row holds siblings * 2 + 5 controls at every page. When the pager is too narrow for that row, it shows previous, "Page 3 of 10", next instead; more siblings switch sooner.
totalnumberNoneTotal number of rows. With pageSize, renders the "1 to 10 of 240" count. A total of 0 reads "No results".
pageSizenumberNoneRows per page. Needed for the count, and it is the current value of the size select.
pageSizeOptionsnumber[]NoneOffering these renders the rows-per-page select. Needs onPageSizeChange.
onPageSizeChange(pageSize: number) => voidNoneCalled with the newly chosen page size. Send the user back to page 1 here.
renderPage(page: number) => ReactElementNoneFor URL paging: return the element a page control should render as, e.g. (page) => <Link href={?page=${page}} />. It takes the button's styling and its label.
page*numberThe current page, 1-based.
pageCount*numberHow many pages there are in total.
onPageChange(page: number) => voidCalled with the page that was clicked. Omit it for link paging (renderPage); the pager only builds a click handler when it was given one.
siblingsnumber = 1How many pages to show either side of the current one. The first and last page are always shown, so the row holds siblings * 2 + 5 controls at every page. When the pager is too narrow for that row, it shows previous, "Page 3 of 10", next instead; more siblings switch sooner.
totalnumberTotal number of rows. With pageSize, renders the "1 to 10 of 240" count. A total of 0 reads "No results".
pageSizenumberRows per page. Needed for the count, and it is the current value of the size select.
pageSizeOptionsnumber[]Offering these renders the rows-per-page select. Needs onPageSizeChange.
onPageSizeChange(pageSize: number) => voidCalled with the newly chosen page size. Send the user back to page 1 here.
renderPage(page: number) => ReactElementFor URL paging: return the element a page control should render as, e.g. (page) => <Link href={?page=${page}} />. It takes the button's styling and its label.