Pagination

Moves through a long list one page at a time.

Examples

With a count

51–100 of 60,183 conversations

Without a total

101–150 messages

States

  • rest

  • hover

  • focus-visible

  • current

  • disabled

Usage

When to use it
Use
A list or table has more rows than one page shows and the person needs to move through them.
Use
The total is known (numbered) or only a next cursor is (Previous and Next).
Not for
Switching between views of the same data. Use Tabs.
Not for
The list loads as the person scrolls and has no pages. Use Table.

Rules

6 rules
Must
Give every page a real href through getHref or hrefPattern, even with client routing. Because without href the PaginationLink anchor takes no focus and cannot open in a new tab.
Never
Render every page number. Use paginationRange's seven slots. Because a thousand links wrap the row, move Next, and make a keyboard user tab through every one.
Must
Keep a fixed slot count and pass the same minWidth to every number in a custom row. Because a slot that widens at page 10 moves Next under the pointer, so the next click lands on a number.
Should
Name the thing in PaginationInfo and use one number format with separators.
Must
Wrap PaginationInfo in role="status" when the page changes without a full navigation. Because a client page change moves no focus, so a screen reader hears nothing change.
Should
Let the row compact when the full row does not fit its container (automatic); do not force the text back.

Accessibility

5 checks
The row is a nav landmark; page links are plain anchors that open in a new tab.
Tab goes Previous, each page, Next; disabled ends are skipped.
The current page sets aria-current="page".
When the full row does not fit its container, it compacts to arrows and "Page 4 of 24".
Announce PaginationInfo in a status region when the page changes in place.

Edge cases

7 edge cases
1,204,000 pages
Every slot widens to fit the largest number, all equal.
Cursor API
Previous, the page, Next; no total.
One page
Previous and Next disabled, one current slot.
Zero pages
No slots, both ends disabled; hide pagination when the list is empty.
Right to left
Previous sits on the right and its caret mirrors.
Page fails to load
Show Empty state reason="error" in the list; the row stays.
Ten clicks on Next
Next never moves, so every click lands on it.

API

8 props
PaginationNav page
number default required
PaginationNav pages
number
PaginationNav getHref
(page) => string
PaginationNav hrefPattern
string default '?page={page}'
PaginationNav hasNext
boolean
PaginationNav onPageClick
(page, event) => void
PaginationLink isActive, disabled, size
boolean, boolean, Button size default size 'icon'
PaginationInfo from, to, total, noun, page
number, number, number?, string?, number?

Tokens

14 tokens
--line-control
oklch(var(--ink-c) / 0.16)
--line-control-hover
oklch(var(--ink-c) / 0.32)
--surface-hover
oklch(var(--ink-c) / 0.05)
--surface-selected
oklch(var(--ink-c) / 0.09)
--text-1
var(--ink)
--text-disabled
oklch(var(--ink-c) / 0.40)
--radius-soft
6px
--control
32px
--type-body-sm
380 13px/18px var(--font-sans)
--text-3
oklch(var(--ink-c) / 0.66)
--features-data
"tnum", "zero", "ss04"
--space-4
16px
--dur-fast
120ms
--ease-step
cubic-bezier(0.2, 0, 0, 1)

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.