Pagination
A React pagination component whose current-page pill slides between numbers, with expandable ellipses that stagger in hidden pages, a compact “3 / 12” variant with rolling digits, arrow-key navigation and an SEO-friendly link mode.
A pager for lists, search results and product grids. The dark current-page pill springs from number to number while the page label inverts beneath it. With many pages, the first and last pages stay pinned, a few siblings flank the current page and the rest collapse into “…” — click an ellipsis and the hidden pages fade in one after another. When space is tight, switch to the compact variant, which shows only “3 / 12” and rolls each digit up or down as you page. The page supports controlled and uncontrolled modes; pass getHref and every page renders as a real link that search engines can crawl.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add pagination.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<Pagination/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/pagination.en.jsonOr, once registries are configured in components.json, install it as @webberui/pagination.
Usage
import { Pagination } from "@/components/ui/pagination";
const [page, setPage] = React.useState(1);
<Pagination page={page} onPageChange={setPage} total={24} />
// Compact variant: shows only “3 / 12”
<Pagination compact page={page} onPageChange={setPage} total={12} />
// Link mode: every page is an <a href> that search engines can crawl
<Pagination
defaultPage={1}
total={24}
getHref={(p) => `/products?page=${p}`}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
page | number | — | Controlled current page (1-based); when omitted the component manages it (uncontrolled) |
defaultPage | number | 1 | Initial page in uncontrolled mode (1-based) |
onPageChange | (page: number) => void | — | Called when the page changes, with the new page |
total | number | — | Total number of pages (required) |
siblingCount | number | 1 | How many pages to show on each side of the current page |
boundaryCount | number | 1 | How many pages to pin at the start and the end |
compact | boolean | false | Compact variant: only “3 / 12” plus previous/next, with rolling digits |
showFirstLast | boolean | false | Show «/» buttons that jump to the first/last page |
prevLabel | string | "上一頁" (Previous) | Previous button text (icon only at phone widths; the text becomes the aria-label) |
nextLabel | string | "下一頁" (Next) | Next button text (icon only at phone widths; the text becomes the aria-label) |
getHref | (page: number) => string | — | When provided, every page renders as an <a href>; otherwise buttons |
duration | number | 200 | Fade and hover transition time (ms), also written to --wb-pagination-duration |
stagger | number | 30 | Delay between pages fading in when an ellipsis expands (ms) |
stiffness | number | 500 | Spring stiffness for the current-page pill and rolling digits |
damping | number | 35 | Spring damping for the current-page pill and rolling digits |
aria-label | string | "分頁" (Pagination) | Accessible name of the nav |
className | string | — | Passed through to the outer nav |
The pure function getRange(page, total, siblingCount, boundaryCount) is also a named export. It returns (number | "ellipsis-left" | "ellipsis-right")[], so you can reuse the same page algorithm on the server or in a custom layout.
How it works
- Page algorithm:
boundaryCountpages stay pinned at each end andsiblingCountpages flank the current page; when total is belowboundaryCount × 2 + siblingCount × 2 + 3, every page is shown. Near either end the sibling window shifts inward so the list keeps a constant item count; an ellipsis only appears when it hides at least two pages. - The current-page pill is a single element with a shared
layoutId; on page change it springs (stiffness/damping) to its new spot. When the list changes, the other items reflow with layout animation and newly shown pages fade in with a slight scale. - Clicking “…” replaces it with the pages it stands for, fading them in at
staggerintervals starting from the page nearest the current one. If more than 10 pages are hidden, only the nearest 10 expand and an ellipsis remains for the rest — click it again to keep going. The expansion resets after a page change. - In the compact variant each digit is its own rolling column, and only the digits that change move: paging forward rolls the new digit up from below, paging back rolls it down from above.
- With
getHref, pages are ordinary links: clicking navigates as usual and also callsonPageChange. With a framework router, handle navigation inonPageChangeyourself. - Timing variable: the root element sets
--wb-pagination-duration(default 200ms), used by the hover background transition; override it in global styles.
Accessibility
- The root is a
<nav>named byaria-label(default “分頁” — Pagination); the current page hasaria-current="page"and every page has anaria-labelof “Page N” (第 N 頁 in the default Chinese build) - Each ellipsis is a button whose
aria-labelsays which pages it will reveal (for example “Show pages 4 to 9”) - On the first page Previous, and on the last page Next, use
aria-disabledrather thandisabled, so they stay focusable and screen readers announce them as dimmed - With any pagination button focused, ← / → go to the previous/next page and Home/End jump to the first/last page; focus follows the new current page
- The compact variant announces “Page 3 of 12” through visually hidden
aria-live="polite"text, and the rolling digits are hidden from assistive technology - When the user has reduce motion turned on, the pill jumps straight to its new spot, expanded pages appear without staggering and the compact digits swap without rolling
Dropdown Menu
A button-triggered dropdown menu with groups, separators, submenus, checkbox and radio items and shortcut hints, a sliding highlight, and full keyboard and typeahead support.
Menubar
A desktop-style menubar: once a menu is open, hovering a neighbour switches instantly and the panel slides to its new position instead of closing and reopening, with full arrow-key navigation.