Pagination

Moves a reader through a finite, ordered collection: eight assembled forms over one headless namespace, all controlled by your state.

Demo

The six paging forms, each with its own props. The page is real state here, so the numbers, arrows, and jumps all move.

Component
total
siblingCount
import { useState } from "react"; import { PaginationPageDefault } from "@/components/application/pagination/pagination"; const [page, setPage] = useState(1); <PaginationPageDefault page={page} onPageChange={setPage} total={10} />

Controlled, always

page is a value you own and onPageChange is the only way it changes. Rendering a form without a handler is not a read-only mode, it is a control that ignores every click.

Contextual demo

Pagination is the last row of something else, so the thing it pages through is most of the argument. It has a screen.

The rows really move

Each scenario slices its own fixture, so paging changes what is above the control rather than only what is inside it. The advanced and minimal scenarios go further and re-slice on the rows-per-page select, which is work the component leaves to you: it holds the value and reports the change, and stops there.

The family

Eight assembled forms and the headless namespace they are all built from. Four files, one import path each.

The components in this family, with their descriptions and import paths
ComponentDescriptionImport from
PaginationPageDefaultPage-level: numbered cells centered, with Previous and Next as link-gray buttons at the edges.@/​components/​application/​pagination/​pagination
PaginationPageMinimalCenterThe same layout with Previous and Next as outlined secondary buttons.@/​components/​application/​pagination/​pagination
PaginationCardDefaultThe card footer form: the same three parts inside the card’s own horizontal padding.@/​components/​application/​pagination/​pagination
PaginationCardMinimalNo numbers. Previous, Next, a page count, and a rows-per-page select, in three alignments.@/​components/​application/​pagination/​pagination
PaginationButtonGroupTriggers and numbers joined into one ButtonGroup, aligned left, center, or right.@/​components/​application/​pagination/​pagination
PaginationCardAdvancedThe data-table form: a page field you can type into, rows per page, and first and last jumps.@/​components/​application/​pagination/​pagination
PaginationDotThe carousel indicator: one dot per slide, in two sizes, optionally framed and optionally brand-colored.@/​components/​application/​pagination/​pagination-dot
PaginationLineThe same indicator drawn as bars rather than dots. It takes its width from you.@/​components/​application/​pagination/​pagination-line
PaginationThe headless namespace every form above is built from: Root, PrevTrigger, NextTrigger, Item, Ellipsis, and Context.@/​components/​application/​pagination/​pagination-base

Built from

Six pieces make up the forms; four have their own docs page.

Component
Where it’s used here
ButtonPrevious, Next, and PaginationCardAdvanced’s four chevrons. color="link-gray" on PaginationPageDefault’s desktop pair only; its own mobile pair and every other Button in the family are color="secondary". Always size="sm". PaginationButtonGroup uses ButtonGroupItem instead and no Button at all.
ButtonGroupPaginationButtonGroup only, joining the triggers and every number cell into one group.
SelectThe rows-per-page control in PaginationCardMinimal and PaginationCardAdvanced, wired with the component’s current value and onChange props. PaginationCardMinimal also sets popoverClassName, as of 2026-08-22, so its list grows to fit “100 per page” with the trigger’s width as the floor.
InputBasePaginationCardAdvanced’s page field, with field-sizing-content so it grows with the number.

Page forms

Two forms for pagination under a page’s content: a top border and nothing else, so the content column sets their width.

PaginationPageDefault: Previous and Next as link-gray buttons
PaginationPageDefault, rounded: the sheet’s Shape=Circle
PaginationPageMinimalCenter: the same layout, outlined triggers
PaginationPageMinimalCenter, rounded

Current and hover are the same fill

The current cell is painted bg-primary_hover, which is the token hover uses and also the exact color of bg-secondary. Measured in the browser: on a secondary surface the current page has no contrast at all. Every demo on this page therefore sits on the primary surface, which is where a real one belongs too.

Minimal center is the more emphatic of the two

The name says minimal, but its triggers are outlined secondary buttons while the default’s are text links. Read it as “the numbers are the minimum, the triggers are buttons”, and pick the default when the pagination should stay quiet.

Card forms

Four forms for the last row of a card. Each supplies the top border and the card’s padding, so they sit inside one.

Table rows

PaginationCardDefault

Table rows

PaginationCardDefault, rounded

Table rows

PaginationCardMinimal, align left: no numbers at all

Table rows

PaginationCardMinimal, align center

Table rows

PaginationCardMinimal, align right

Table rows

PaginationButtonGroup, align left

Table rows

PaginationButtonGroup, align right

Table rows

PaginationButtonGroup, align center: no counterpart on the sheet

Table rows

PaginationCardAdvanced, align space-between

Table rows

PaginationCardAdvanced, align center

align names the pair, not the whole control

On PaginationCardMinimal, align says where Previous and Next sit; the page count and the select take the other end. On PaginationCardAdvanced the same prop takes a different pair of values, space-between and center, and moves three clusters rather than one.

States

Ordinary buttons, so the usual four states. What is worth seeing is where the ends and the collapse fall.

Page 1: Previous is disabled, and the run starts at the beginning
Page 10 of 10: Next is disabled
Page 12 of 24: an ellipsis each side, and the cell count holds at 7
siblingCount 2, page 12 of 24: 9 cells instead of 7
Hover and focus paint bg-primary_hover; current adds a ring so it stays distinct from both

The mobile state is a different control, not a smaller one

Below 768px four of the six swap to the same mobile row: an arrow, “Page 3 of 10”, an arrow. PaginationCardAdvanced keeps its first-page and last-page jumps as well, so it shows four buttons around the count. PaginationButtonGroup keeps its one layout and narrows it: below 768px the words leave its two triggers and siblingCount drops to 0, so the seven cells become five, “1 2 3 … 10” at page 1, which measured 254px at 390×844 on 2026-08-22 where it had been 324px. The first paint seeds desktop, so a narrow client draws nine items and settles to seven after mount. All of it is a media query on the window, so this page cannot show it beside the desktop form: narrow the browser, or open the contextual demo.