v1.0

Pagination

Numbered navigation for splitting long lists across pages.

Pro

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

pnpm dlx @create-ui/cli add pagination

Anatomy

Pagination works two ways. The data-driven form takes totalPages and a page binding and builds the controls itself:

<Pagination variant="compact" defaultPage={3} totalPages={10} />

The composed form lets you place each part by hand for full control:

<Pagination>
  <PaginationContent>
    <PaginationFirst />
    <PaginationPrevious />
    <PaginationLink />
    <PaginationEllipsis />
    <PaginationNext />
    <PaginationLast />
  </PaginationContent>
</Pagination>

Usage

import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationFirst,
  PaginationLast,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination"

Data-driven — pass totalPages and let the component assemble the window, ellipses, and directional buttons:

<Pagination page={page} totalPages={24} onPageChange={setPage} />

Composed — lay out the controls yourself when you need an exact set:

<Pagination>
  <PaginationContent>
    <PaginationPrevious />
    <PaginationLink isActive>1</PaginationLink>
    <PaginationNext />
  </PaginationContent>
</Pagination>

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.

Pro

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.

Page 4 of 12

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.

Page 3 of 10

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.

Pro

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.

Pro

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.

Pro

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.

Pro

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.

Pro

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.

Pro

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.

KeyDescription
TabMoves focus to the next control in the pagination.
Shift + TabMoves focus to the previous control.
Enter / SpaceActivates the focused link or button.

ARIA notes:

  • The root sets role="navigation" and aria-label="pagination"; do not wrap it in another nav landmark.
  • The active page is identified with aria-current="page". When using asChild on PaginationLink, mirror that attribute on the inner anchor so it survives the slot swap.
  • PaginationFirst, PaginationPrevious, PaginationNext, and PaginationLast set descriptive aria-labels ("Go to first page", etc.). Override with your own aria-label for localized copy.
  • In the auto API (both tiers), the component renders an off-screen aria-live="polite" "Page X of Y" status unless you pass getPageHref (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()):

<Pagination className="w-full justify-between">

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="pagination" on the root <nav>, with data-variant="<variant>" and data-shape="<shape>".
  • data-slot="pagination-content" on the inner wrapper (forwarded to ButtonGroup in grouped layouts).
  • data-slot="pagination-link" on numbered links, with data-active when isActive is 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" and data-slot="pagination-end" on the directional clusters, data-slot="pagination-numbers" on the centered number group, and data-slot="pagination-select" on the page-jump / page-size selectors.

Target a specific state in CSS:

[data-slot="pagination-link"][data-active] {
  /* style the active page */
}
  • 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

PropTypeDefaultDescription
totalPagesnumber-Enables the auto API; the component builds the links, window, and buttons.
pagenumber-Controlled current page (auto API).
defaultPagenumber1Uncontrolled initial page (auto API).
onPageChange(page: number) => void-Fires when the page changes (auto API).
minPagenumber1Lowest page number; also the first-page target (auto API).
getPageHref(page: number) => string-Renders every control as an anchor for SSR / no-JS navigation (auto API).
siblingCountnumber1Pages shown on each side of the current page (auto API).
boundaryCountnumber1Pages pinned at the start and end before an ellipsis (auto API).
pageSizenumber-Controlled rows-per-page for the data-table layout. (Pro)
defaultPageSizenumber20Uncontrolled initial rows-per-page. (Pro)
onPageSizeChange(size: number) => void-Fires when the per-page selector changes. (Pro)
pageSizeOptionsnumber[][10, 20, 30, 50]Options in the per-page selector. (Pro)
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-A single PaginationContent holding the controls (compound API).

Variants

VariantOptionsDefaultDescription
variant"compact" "compact-grouped" "full" "split" "data-table" "grouped" "full-pager" "compact-pager""compact-grouped"Layout treatment. compact and compact-grouped are free; the rest are Pro.
shape"rounded" "pill""rounded"Corner radius. pill makes every control fully rounded.

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

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The directional buttons, numbered links, and ellipses, in order.

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

PropTypeDefaultDescription
isActivebooleanfalseMarks this link as the current page. Drives data-active and aria-current="page".
asChildbooleanfalseRender as the child element via Radix Slot. Use to swap in an anchor or router link.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The page numeral (or an anchor wrapping the numeral when asChild is set).

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

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element via Radix Slot. Use to swap in an anchor or router link.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Optional override for the default icon.

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

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element via Radix Slot. Use to swap in an anchor or router link.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Optional override for the default icon.

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

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element via Radix Slot. Use to swap in an anchor or router link.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Optional override for the default icon.

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

PropTypeDefaultDescription
asChildbooleanfalseRender as the child element via Radix Slot. Use to swap in an anchor or router link.
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-Optional override for the default icon.

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

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode"..."Optional override for the visible glyph (the screen-reader hint is always rendered).

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

PropTypeDefaultDescription
pageReact.ReactNode-Current page value shown (or seeded into the input when editable).
totalReact.ReactNode-Total page count shown after "of".
labelReact.ReactNode"page"Leading label; pass null to hide it.
editablebooleanfalseRender the page value as a numeric Input instead of text.
inputPropsReact.ComponentProps<typeof Input>-Props forwarded to the input when editable (e.g. onKeyDown).
classNamestring-Tailwind classes merged with the component's CVA classes via cn().

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

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The first / previous controls.

PaginationEnd (Pro)

Right-hand cluster for the next / last controls, mirroring PaginationStart. Writes data-slot="pagination-end". Extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The next / last controls.

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">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the component's CVA classes via cn().
childrenReact.ReactNode-The start cluster, numbered links, and end cluster.