Navigation
Pagination
Move through a result set that already knows its size, with the page sizes people actually use.
Examples
A summary and the pages
The row count belongs beside the controls: the reader answers "is the thing I want on this page" before deciding whether to move.
61–80 of 480 rows
<PaginationSummary from={61} to={80} total={480} unit="rows" />
<Pagination page={page} pageCount={24} onPageChange={setPage} />Compact, for a footer
Arrows and a status: the shape a card footer or a mobile toolbar has room for, with both end jumps still one click away.
Prepared locally · 480 rows
<Pagination page={page} pageCount={24} onPageChange={setPage} compact />Neighbours, and the end jumps
`siblings` widens the window; `edges` is the pair of double chevrons that jumps to either end. The page numbers themselves are derived — never a list you maintain.
siblings = 2
edges = false
<Pagination page={page} pageCount={24} onPageChange={setPage} siblings={2} />
<Pagination page={page} pageCount={24} onPageChange={setPage} edges={false} />At the ends, and when the total changes
The first page, the last page, and a set that fits on one: the arrows disable themselves and the window collapses to the pages that exist. Lower the page count while you are deep in the set and the position clamps instead of pointing at a page that is gone.
Page count
First page
Last page
/* One page: nothing to disable, and the set is still named. */
<Pagination page={1} pageCount={1} onPageChange={setPage} />
/* The window is re-derived whenever pageCount changes. */
<Pagination page={page} pageCount={pageCount} onPageChange={setPage} />Usage
import { Pagination, PaginationSummary } from "@luc/ui";<Pagination page={page} pageCount={24} onPageChange={setPage} />1-based `page` and a `pageCount`; everything else — the window, the gaps, the disabled arrows — is derived.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
`paginationWindow` is exported and pure because the window *is* the component: a caller that needs to know which pages are on screen — to map a keyboard shortcut, or to prefetch — should call it rather than write a second version of the rule.
| Export | Kind | Signature |
|---|---|---|
Pagination | 9 documented props | ({ className, page, pageCount, siblings, edges, compact, onPageChange, label, previousLabel, nextLabel, ...props }: PaginationProps) => import("@types/react").JSX.Element |
PaginationSummary | 4 documented props | ({ className, from, to, total, unit, ...props }: PaginationSummaryProps) => import("@types/react").JSX.Element |
paginationWindow | export | (page: number, pageCount: number, siblings?: number) => PageToken[] |
Pagination props
Type: PaginationProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
compact? | boolean | undefined | — | Replaces the window with arrow controls only — for a narrow container or a mobile toolbar. |
edges? | boolean | undefined | — | Show first/last controls. Default `true`. |
label? | string | undefined | — | Accessible name. Every pagination landmark needs one. |
nextLabel? | string | undefined | — | |
onPageChange? | ((page: number) => void) | undefined | — | |
page | number | — | Current page, 1-based. |
pageCount | number | — | Total pages. `0` and `1` both render a single, current page. |
previousLabel? | string | undefined | — | Text used inside the "previous" control; hidden below `sm`. |
siblings? | number | undefined | — | Neighbours shown on each side of the current page. Default 1. |
PaginationSummary props
Type: PaginationSummaryProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
from | number | — | First row number on the current page, 1-based. |
to | number | — | Last row number on the current page, 1-based. |
total | number | — | Total rows. |
unit? | string | undefined | — | Noun for the rows. Default "results". |
Guidelines
Do
- Let the component derive the window: `siblings` is the only knob a breakpoint needs, and a hand-built page array cannot re-derive itself for a narrower container.
- Name the landmark for the collection it pages (`label="Trades"`), and put the row summary next to the controls with `PaginationSummary`.
- Reach for `compact` in a card footer or a mobile toolbar, where the arrows and the `12 / 24` status are the whole control.
Don't
- Do not re-derive the window yourself to predict which pages are on screen — `paginationWindow` is exported and pure, and two implementations of one rule will disagree.
- Do not hide the pager when there is only one page: a stable footer is worth more than four saved pixels, and the layout moving on the first search result is worse.
- Do not use it to scroll a region: paging a result set and scrolling a container are different jobs, and the second one is `ScrollArea`.
Accessibility
The control is a named `nav` and the current page is the only element carrying `aria-current="page"`; every numbered control has an accessible name (`Page 24`), the first/last arrows are disabled at the ends they cannot move from, and the Previous/Next words are hidden below `sm` while their icons keep the names. `PaginationSummary` groups digits with a fixed locale, so the server and the browser cannot render `1,284` two different ways.
Related