Pagination
Navigate multi-page lists with previous/next controls, numbered links, and ellipsis.
Composable pagination for tables and lists: a nav wrapper, list semantics for items, PaginationLink built from Button as a link, chevron controls from @by/icons, and optional usePagination to derive which page numbers and ellipsis to show. This is not a data fetcher—pair it with your router or data layer.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition for a typical bar (one PaginationItem per control or ellipsis):
Pagination
└── PaginationContent
├── PaginationItem
│ └── PaginationPrevious (optional)
├── PaginationItem
│ └── PaginationLink (optional; repeat)
├── PaginationItem
│ └── PaginationEllipsis (optional)
└── PaginationItem
└── PaginationNext (optional)Pair with usePagination when you want derived pages slices and handlers instead of wiring counts by hand. Semantics follow the HTML <nav>, <ul> / <li>, and <a> elements—there is no Radix primitive.
Usage
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
usePagination,
} from '@by/experience-system';Pagination and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="/items?page=1" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/items?page=1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="/items?page=3" />
</PaginationItem>
</PaginationContent>
</Pagination>Examples
Simple number row
Use PaginationLink alone when you only need a strip of page numbers (no prev/next).
Toolbar layout
Pair PaginationPrevious and PaginationNext with surrounding controls—for example row-size Select—and reset Pagination width with className (mx-0 w-auto) when aligning in a toolbar.
Using the pagination hook
Drive the UI from usePagination: it exposes pages (numbers and 'ellipsis'), currentPage, navigation handlers, and optional onPaginationChange. When there are at most five total pages, every page is listed; otherwise a sliding window with ellipsis is used.
API Reference
Pieces are implemented in packages/experience-system/src/components/Pagination/Pagination.tsx and usePagination.ts. There is no Radix wrapper—the tables mirror React.ComponentProps for the underlying HTML elements plus Experience System additions (data-slot, isActive, Button styling via PaginationLink). For native attributes not listed here, see MDN for <nav>, <ul>, <li>, <a>, and <span>.
Pagination
Root <nav> wrapper. Forwards React.ComponentProps<'nav'>.
| Prop | Type | Default |
|---|---|---|
role | string | 'navigation' (implementation default) |
aria-label | string | 'pagination' (implementation default) |
The implementation also applies layout classes (mx-auto flex w-full justify-center) unless overridden by className.
| Data attribute | Values |
|---|---|
data-slot | pagination |
PaginationContent
Container <ul> for the row of items. Forwards React.ComponentProps<'ul'>.
| Data attribute | Values |
|---|---|
data-slot | pagination-content |
PaginationItem
Wrapper <li> for each control. Forwards React.ComponentProps<'li'>.
| Data attribute | Values |
|---|---|
data-slot | pagination-item |
PaginationLink
Page link: Experience System Button variant="ghost" with asChild wrapping an <a>. Accepts isActive for current page styling and aria-current="page".
| Prop | Type | Default |
|---|---|---|
isActive | boolean | — |
size | Same as Button size | icon |
Also forwards React.ComponentProps<'a'> (for example href, onClick, className).
| Data attribute | Values |
|---|---|
data-slot | pagination-link |
data-active | Present when isActive is true |
PaginationPrevious
Built on PaginationLink with a leading chevron icon and sr-only label text. Forwards the same anchor PaginationLink props plus text for the visually hidden label.
| Prop | Type | Default |
|---|---|---|
text | string | 'Previous' |
Sets aria-label="Go to previous page" on the underlying link.
PaginationNext
Built on PaginationLink with a trailing chevron icon and sr-only label text.
| Prop | Type | Default |
|---|---|---|
text | string | 'Next' |
Sets aria-label="Go to next page" on the underlying link.
PaginationEllipsis
Non-interactive gap indicator. Forwards React.ComponentProps<'span'> except the visible ellipsis icon is aria-hidden; screen readers get sr-only “More pages”.
| Data attribute | Values |
|---|---|
data-slot | pagination-ellipsis |
usePagination
Headless hook for page math and handlers. Use with the components above.
Parameters
| Param | Type | Default |
|---|---|---|
totalItems | number | (required) |
itemsPerPage | number | 5 |
initialPage | number | 1 (when omitted, internal state starts at 1) |
onPaginationChange | (page: number) => void | — |
Returns
| Field | Type |
|---|---|
currentPage | number |
numberOfPages | number |
hasPrevious | boolean |
hasNext | boolean |
pages | (number | 'ellipsis')[] |
handleNextPage | () => void |
handlePreviousPage | () => void |
handleSelectPage | (page: number) => void |
Accessibility
- The root
navis labeled for pagination; the active page usesaria-current="page"onPaginationLinkwhenisActiveis set; previous/next exposearia-label; ellipsis hides decorative glyphs from the accessibility tree while retaining “More pages” for assistive tech. - Chevron icons flip on
rtlvia Tailwind utilities on the primitives; setdir="rtl"on a parent so layout and icons stay consistent with your locale. - Ensure each
PaginationLinkhas a meaningfulhrefor appropriateonClickrouting without breaking keyboard expectations for in-page navigation. - Follow the WAI-ARIA pagination pattern when wiring labels, landmark usage, and focus order in your app shell.
Keyboard interactions
| Key | Description |
|---|---|
Tab | Moves focus between focusable controls (PaginationLink, previous/next) in tab order. |
Enter / Space | Activates the focused link (native <a> behavior). |
Source in the repo: packages/experience-system/src/components/Pagination/Pagination.tsx and usePagination.ts. Agent-oriented contracts: packages/experience-system/src/components/Pagination/Pagination.instructions.md.