Numbered navigation for splitting long lists across pages.
Description
Pagination is a compound component that renders a <nav role="navigation" aria-label="pagination"> containing numbered links and direction controls. It is composed of PaginationContent, PaginationLink, PaginationEllipsis, and four directional buttons (PaginationFirst, PaginationPrevious, PaginationNext, PaginationLast). The compact-grouped variant delegates to ButtonGroup for a single connected surface; the compact variant lays the controls out as standalone buttons with gaps.
Reach for it on long tables, search results, archives, and report views where the user needs to scan ranges and jump to specific pages. Pair it with a row-count or page-size selector when the surface has room. The visible page set is yours to decide: a typical pattern is first, a window around the current page, an ellipsis, and last.
Pagination is tiered. The free tier ships the compact and compact-grouped layouts. A Pro seat unlocks the full, split, data-table, grouped, full-pager, and compact-pager layouts, the page-size selector, and the PaginationStatus / PaginationStart / PaginationEnd / PaginationNumbers sub-parts. Both tiers share the same data-driven auto API.
Don't use Pagination for infinite-scroll feeds (no controls needed), for switching between sibling panels (use Tabs), or for hierarchical trails like Home / Library / Albums (use Breadcrumb). A single back / forward pair without numbered pages is better served by two Buttons.
Installation
Anatomy
Pagination works two ways. The data-driven form takes totalPages and a page binding and builds the controls itself:
The composed form lets you place each part by hand for full control:
Usage
Data-driven — pass totalPages and let the component assemble the window, ellipses, and directional buttons:
Composed — lay out the controls yourself when you need an exact set:
Examples
The free previews below run on the base component. Previews stamped Pro need a seat: add pagination with a Pro seat swaps in the richer overlay (same API, extra variant options and sub-parts).
Variants
variant picks the layout. Free ships two: compact-grouped (default) delegates to ButtonGroup, fusing every control into one connected surface, while compact renders the same controls as standalone buttons separated by small gaps.
A Pro seat unlocks six more layouts for richer table and toolbar footers — full, split, data-table, grouped, full-pager, and compact-pager. Each one is shown under Pro layouts below.
Shape
shape controls corner radius on every layout. rounded (default) follows the size scale; pill makes every control fully rounded. pill reads well on cards and toolbars; rounded fits table footers and dense layouts.
It applies to the Pro layouts too — here rounded and pill on the full variant, including its status and page-jump controls.
Auto Pagination
The data-driven path in depth. Drive the current page with onPageChange for client state, or with getPageHref to render every control as an anchor for SSR / no-JS navigation. Tune how many numbers show around the current page with siblingCount, and how many are pinned at each end with boundaryCount.
With Jump Buttons
Add PaginationFirst and PaginationLast at the ends when the range is long enough that jumping to the extremes saves clicks. For short ranges (under ~5 pages) the jump buttons are overkill; rely on PaginationPrevious and PaginationNext alone.
Disabled at Boundaries
Pass the native disabled attribute to mark directional buttons as unavailable. The typical pattern: disable PaginationFirst and PaginationPrevious on page 1, and PaginationNext and PaginationLast on the last page. Visuals (pointer, color, focus) come from the underlying button.
Controlled
Drive the current page from a useState and pass isActive to the matching PaginationLink. Each control wires its own onClick to move state, and the directional buttons clamp to 1 and total. Build your visible page window however you want; a 1 ... n-1 n n+1 ... total pattern covers most table views.
Composition with Router Link
Every clickable part accepts asChild, so you can hand off rendering to a router link (<a href>, next/link, react-router's Link) without losing the styling or ARIA wiring. When using asChild on PaginationLink, set aria-current="page" on the inner anchor for the active page so it still announces correctly.
Pro layouts
A developer seat swaps in the Pro overlay. It keeps the same data-driven and composed APIs and adds six table- and toolbar-ready layouts. Each preview below is driven by the auto API — pass variant plus totalPages and a page binding, and the component assembles the whole footer (status, numbers, clusters, selects) for you.
Full
variant="full" spreads a PaginationStatus label, the numbered controls, and a page-jump Select across the full width. Use it on report and list footers where there is horizontal room and users want to type or pick a page directly. The previous/next buttons carry a text label that collapses to an icon on narrow viewports.
Split
variant="split" pins the first/previous cluster to the leading edge and the next/last cluster to the trailing edge, with the numbered window centered between them. It reads well on wide list footers where the controls should hug the margins.
Data Table
variant="data-table" pairs an editable page input (type a number, press Enter) with a per-page Select driven by pageSize, onPageSizeChange, and pageSizeOptions. This is the standard footer for dense data grids.
Grouped
variant="grouped" clusters the directional controls into two connected ButtonGroups around a centered number group — a compact, toolbar-style footer without the full-width spread.
Full Pager
variant="full-pager" drops the numbered window and keeps a first/previous cluster, a centered page X of Y count, and a next/last cluster. It's a lightweight pager for surfaces where per-page numbers aren't needed.
Compact Pager
variant="compact-pager" folds the controls and a page X of Y status chip into a single connected group — the tightest footprint, for cards and narrow toolbars.
Accessibility
Pagination renders a real <nav> with aria-label="pagination", so screen readers announce it as a navigation landmark. Each directional button (First, Previous, Next, Last) ships with a built-in aria-label. The active PaginationLink writes aria-current="page" automatically when isActive is set, and PaginationEllipsis is marked role="presentation" with a paired .sr-only "More pages" hint.
ARIA notes:
- The root sets
role="navigation"andaria-label="pagination"; do not wrap it in another nav landmark. - The active page is identified with
aria-current="page". When usingasChildonPaginationLink, mirror that attribute on the inner anchor so it survives the slot swap. PaginationFirst,PaginationPrevious,PaginationNext, andPaginationLastset descriptivearia-labels ("Go to first page", etc.). Override with your ownaria-labelfor localized copy.- In the auto API (both tiers), the component renders an off-screen
aria-live="polite""Page X of Y" status unless you passgetPageHref(anchor navigation reloads, so no live region is needed). - For long, dynamic ranges without the auto API, announce page transitions on the region the pagination controls (e.g. set
aria-live="polite"on the result list), not on the pagination itself.
Styling
Tailwind override: pass className to merge Tailwind classes with the component's CVA classes (via cn()):
Data slots and attributes: the component sets these for CSS targeting:
data-slot="pagination"on the root<nav>, withdata-variant="<variant>"anddata-shape="<shape>".data-slot="pagination-content"on the inner wrapper (forwarded toButtonGroupin grouped layouts).data-slot="pagination-link"on numbered links, withdata-activewhenisActiveis set.data-slot="pagination-first",data-slot="pagination-previous",data-slot="pagination-next",data-slot="pagination-last"on the matching directional buttons.data-slot="pagination-ellipsis"on the truncation marker.- Pro sub-parts:
data-slot="pagination-status"on the status / page-input block,data-slot="pagination-start"anddata-slot="pagination-end"on the directional clusters,data-slot="pagination-numbers"on the centered number group, anddata-slot="pagination-select"on the page-jump / page-size selectors.
Target a specific state in CSS:
Related Components
- ButtonGroup: the primitive that the grouped layouts delegate to; use it directly for non-numbered segmented controls.
- Tabs: use this when the controls switch between sibling content panels in place rather than paging through a list.
- Breadcrumb: use this for hierarchical location trails, not paged sequences.
- Button: use a single Prev / Next pair (two
Buttons) when there are no numbered pages to render.
API Reference
Pagination
The root container. Renders a <nav role="navigation" aria-label="pagination">, broadcasts variant and shape to every part through context, and writes data-slot="pagination" plus data-variant and data-shape. Provide children for the compound API, or totalPages for the data-driven auto API (the two are mutually exclusive). Extends React.ComponentProps<"nav">, so any standard <nav> attribute (id, role, aria-*, onClick, etc.) is accepted.
Props
Variants
PaginationContent
The inner wrapper that holds the controls. In grouped layouts this renders a ButtonGroup with variant="soft", size="md", and the shape inherited from the root. Otherwise it renders a <div> with inline-flex items-center gap-1. Writes data-slot="pagination-content". Extends React.ComponentProps<"div">.
Props
PaginationLink
A numbered page link. Renders as a <button> by default; pass asChild to swap in an anchor or router link. Writes data-slot="pagination-link", data-active when isActive is set, and aria-current="page" for the active page. Extends Omit<React.ComponentProps<"button">, "type">.
Props
PaginationFirst
Directional button that jumps to the first page. Ships with aria-label="Go to first page" and a built-in RiArrowLeftDoubleFill icon. Override the icon by passing children. Writes data-slot="pagination-first". Extends Omit<React.ComponentProps<"button">, "type">.
Props
PaginationPrevious
Directional button that steps one page back. Ships with aria-label="Go to previous page" and a RiArrowLeftSLine icon. Writes data-slot="pagination-previous". Extends Omit<React.ComponentProps<"button">, "type">.
Props
PaginationNext
Directional button that steps one page forward. Ships with aria-label="Go to next page" and a RiArrowRightSLine icon. Writes data-slot="pagination-next". Extends Omit<React.ComponentProps<"button">, "type">.
Props
PaginationLast
Directional button that jumps to the last page. Ships with aria-label="Go to last page" and a RiArrowRightDoubleFill icon. Writes data-slot="pagination-last". Extends Omit<React.ComponentProps<"button">, "type">.
Props
PaginationEllipsis
Non-interactive truncation marker. Renders a <span role="presentation" aria-hidden="true"> containing the visible glyph and an .sr-only "More pages" hint. Writes data-slot="pagination-ellipsis". Extends React.ComponentProps<"span">.
Props
PaginationStatus (Pro)
Status readout for the current position. In its default form it renders a page X of Y label; with editable it renders a numeric Input so users can type a page. Writes data-slot="pagination-status". Extends Omit<React.ComponentProps<"div">, "children">.
Props
PaginationStart (Pro)
Left-hand cluster for the first / previous controls in split and grouped layouts. Renders a ButtonGroup in clustered layouts, otherwise an inline flex row. Writes data-slot="pagination-start". Extends React.ComponentProps<"div">.
Props
PaginationEnd (Pro)
Right-hand cluster for the next / last controls, mirroring PaginationStart. Writes data-slot="pagination-end". Extends React.ComponentProps<"div">.
Props
PaginationNumbers (Pro)
Centered wrapper that groups the start cluster, numbered links, and end cluster in the data-table layout. Writes data-slot="pagination-numbers". Extends React.ComponentProps<"div">.