Pagination
Moves through a long list one page at a time.
Examples
51–100 of 60,183 conversations
101–150 messages
States
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 pagenumberdefaultrequiredPaginationNav pagesnumberPaginationNav getHref(page) => stringPaginationNav hrefPatternstringdefault'?page={page}'PaginationNav hasNextbooleanPaginationNav onPageClick(page, event) => voidPaginationLink isActive, disabled, sizeboolean, boolean, Button sizedefaultsize 'icon'PaginationInfo from, to, total, noun, pagenumber, 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)