Pagination
Page numbers as small stickers. The current page is inked in; long runs leave a little ellipsis between the pages that matter. Uses the same paper edge, seeded tilt and press feedback as Button.
Server-compatibleWCAG AA
Page 1 of 20
Compact: Page 2 of 17
First and last: Page 12 of 40
Compact with ends: Page 9 of 17
Slow fetch: Page 3 of 12
import { Pagination } from "@plop-ui/react";
// Links: href is evaluated on the server; each destination is a real anchor.
<Pagination page={3} pageCount={20} href={(page) => `/articles?page=${page}`} />
// Buttons: use inside a client component with your own state.
<Pagination page={page} pageCount={20} onPageChange={setPage} />
// Next.js stays in your app:
<Pagination page={page} pageCount={20} as={Link} href={(page) => `/articles?page=${page}`} />
// Tight spaces: "2 of 17" between Previous and Next. Add First/Last for long lists.
<Pagination variant="compact" showEnds page={page} pageCount={17} onPageChange={setPage} />
// While the next page fetches, its sticker shows a spinner.
<Pagination page={page} pageCount={20} loading={isFetching} onPageChange={setPage} />Change every prop live in the playground.
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
| pagerequired | number | — | Current page, starting at 1. Values outside the range are clamped. |
| pageCountrequired | number | — | Total number of pages. Zero renders no pagination. |
| as | ElementType | a | Router link component, e.g. Next.js Link. Used with href. |
| disabled | boolean | false | Disable all navigation. |
| firstLabel | string | First | First control label, with showEnds. |
| formatCount | (page: number, pageCount: number) => string | (page, count) => ${page} of ${count} | The compact variant's count. |
| getPageLabel | (page: number) => string | n => Page ${n} | Accessible name for each numbered page. |
| href | (page: number) => string | none | Build a destination for each page to render links instead of buttons. |
| lastLabel | string | Last | Last control label, with showEnds. |
| loading | boolean | false | The current page is still fetching: it shows a spinner and the nav is `aria-busy`. Other pages stay usable. |
| loadingLabel | string | Loading | Screen reader text while loading. |
| nextLabel | string | Next | Next control label. |
| onPageChange | (page: number) => void | none | Called when a different page is activated. Required for button-mode navigation. |
| previousLabel | string | Previous | Previous control label. |
| seed | string | number | pagination | Stable sticker tilts. |
| showEnds | boolean | false | Add First and Last controls, for long lists where people jump to the ends. |
| variant | "full" | "compact" | full | `full` shows page numbers. `compact` shows "2 of 17" between Previous and Next, for cards and toolbars. |
Also takes every attribute of <nav> (278 more, e.g. onClick, className, aria-*).
Use it well
DoUse links for destinations that have a URL; use buttons for local lists. Keep page controlled by the same state that selects your results.
Don'tUse arrow-key widget navigation or focus a different page after every click. Tab, Enter and Space already work through native links and buttons.
Accessibility
- A nav labelled Pagination contains native buttons or links. Override aria-label when you show more than one pagination landmark.
- The selected page has aria-current=page and an ink fill. Numbered controls have names like Page 3; getPageLabel can translate them.
- Previous and Next are disabled at the bounds. Disabled link destinations render as native disabled buttons without href.
- Ellipses are decorative and have no tab stop. Large page counts render a bounded window rather than every page.
- The compact variant reads its count as plain text in the nav; formatCount translates it. First and Last are disabled at the ends, like Previous and Next.
- loading sets aria-busy on the nav. The current page becomes a busy button whose spinner says loadingLabel; other pages stay usable, so the latest pick wins.
- Controls are at least 44px tall and wrap in narrow containers. Reused Button styles provide token contrast, focus, press feedback and reduced-motion handling.