Pagination
Moving through a long result set, one page at a time.
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/pagination.jsonUsage
import {
Pagination, PaginationContent, PaginationEllipsis, PaginationInfo,
PaginationItem, PaginationLink, PaginationNext, PaginationPrevious,
} from '@/components/ui/pagination'<Pagination>
<PaginationContent>
<PaginationItem><PaginationPrevious href="#page-1" /></PaginationItem>
<PaginationItem><PaginationLink href="#page-1">1</PaginationLink></PaginationItem>
<PaginationItem><PaginationLink href="#page-2" isActive>2</PaginationLink></PaginationItem>
<PaginationItem><PaginationNext href="#page-3" /></PaginationItem>
</PaginationContent>
</Pagination>Base UI has no pagination primitive, for the same reason it has no breadcrumb: there is no
behaviour to own. A pager is a nav around a list of links, and everything it needs — where am I,
where can I go — comes from the markup being right.
Composition
<Pagination> {/* nav, aria-label="Pagination" */}
<PaginationContent> {/* ul */}
<PaginationItem> {/* li */}
<PaginationPrevious /> <PaginationNext /> {/* the edges */}
<PaginationLink /> {/* one page number */}
<PaginationEllipsis /> {/* the pages that were dropped */}
<PaginationInfo> {/* "Showing 51–75 of 412" */}
</Pagination>Two things are easy to get wrong and are therefore built in rather than left to the caller:
- The current page carries
aria-current="page"and is not a link.isActiverenders a<span>, because a link to the page you are already on does nothing and every keyboard user has to pass through it. - Previous and next keep a text label for assistive technology even when only the chevron is drawn.
Everything renders as an anchor with nativeButton={false}, so middle-click, copy link and open
in a new tab all work — which a <button> that navigates would break.
Truncation
Seventeen pages do not fit. Clip the run around the current page and always show both ends: the first and last page are the two people jump to most, and losing them turns a two-click journey into a dozen.
PaginationEllipsis is aria-hidden. It stands for pages that were dropped, which is a fact
about the layout rather than about the data.
At the edges
On page one there is nowhere back to go, and the control says so with aria-disabled rather than
by vanishing. A pager whose shape changes as you move through it puts the next target somewhere
new every time — the reader has to re-aim after every click.
<PaginationPrevious
href={page === 1 ? undefined : `#page-${page - 1}`}
aria-disabled={page === 1 || undefined}
/>aria-disabled rather than disabled on purpose: the control stays focusable, so a keyboard user
tabbing along the pager does not silently skip it and wonder where it went.
The count, not the page
PaginationInfo is not decoration. "Page 3" tells a radiologist nothing about whether their
filter is narrow enough; "Showing 51–75 of 412 signed reports" does. Where you have room, the
total is the more useful of the two numbers.
size="sm" on the controls suits a pager sitting under a dense table.
Compact
A slice viewer does not need thirty page numbers, it needs one step at a time. iconOnly drops
the words and keeps the accessible name, so the controls still announce as "Previous slice".
When iconOnly hides the label, pass an aria-label that says what is being stepped through.
The default is "Go to previous page", which is wrong when the thing moving is a slice, a series
or a study.
API Reference
PaginationLink
Renders an anchor, or a <span> when active.
| Prop | Type | Default | Description |
|---|---|---|---|
isActive | boolean | false | The page you are on. Renders a span and marks it aria-current="page". |
size | "sm" | "md" | "md" | Matches the surrounding density. |
PaginationPrevious and PaginationNext
| Prop | Type | Default | Description |
|---|---|---|---|
iconOnly | boolean | false | Hides the word, keeps the chevron and the accessible name. |
size | "sm" | "md" | "md" | Matches the surrounding density. |
Both also take the usual anchor props — href, onClick, aria-disabled.
Pagination renders a <nav> already labelled "Pagination", PaginationContent a <ul>,
PaginationItem an <li>, PaginationEllipsis an aria-hidden <span>, and PaginationInfo a
<p>.