Experience System
Components

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-system

In 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'>.

PropTypeDefault
rolestring'navigation' (implementation default)
aria-labelstring'pagination' (implementation default)

The implementation also applies layout classes (mx-auto flex w-full justify-center) unless overridden by className.

Data attributeValues
data-slotpagination

PaginationContent

Container <ul> for the row of items. Forwards React.ComponentProps<'ul'>.

Data attributeValues
data-slotpagination-content

PaginationItem

Wrapper <li> for each control. Forwards React.ComponentProps<'li'>.

Data attributeValues
data-slotpagination-item

Page link: Experience System Button variant="ghost" with asChild wrapping an <a>. Accepts isActive for current page styling and aria-current="page".

PropTypeDefault
isActiveboolean
sizeSame as Button sizeicon

Also forwards React.ComponentProps<'a'> (for example href, onClick, className).

Data attributeValues
data-slotpagination-link
data-activePresent 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.

PropTypeDefault
textstring'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.

PropTypeDefault
textstring'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 attributeValues
data-slotpagination-ellipsis

usePagination

Headless hook for page math and handlers. Use with the components above.

Parameters

ParamTypeDefault
totalItemsnumber(required)
itemsPerPagenumber5
initialPagenumber1 (when omitted, internal state starts at 1)
onPaginationChange(page: number) => void

Returns

FieldType
currentPagenumber
numberOfPagesnumber
hasPreviousboolean
hasNextboolean
pages(number | 'ellipsis')[]
handleNextPage() => void
handlePreviousPage() => void
handleSelectPage(page: number) => void

Accessibility

  • The root nav is labeled for pagination; the active page uses aria-current="page" on PaginationLink when isActive is set; previous/next expose aria-label; ellipsis hides decorative glyphs from the accessibility tree while retaining “More pages” for assistive tech.
  • Chevron icons flip on rtl via Tailwind utilities on the primitives; set dir="rtl" on a parent so layout and icons stay consistent with your locale.
  • Ensure each PaginationLink has a meaningful href or appropriate onClick routing 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

KeyDescription
TabMoves focus between focusable controls (PaginationLink, previous/next) in tab order.
Enter / SpaceActivates 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.