Pagination
Page navigation — previous/next, numbered page links, an ellipsis for long ranges, and the active page.
- Status
- Since
0.1.0- Accessibility pattern
- navigation landmark
Last updated
Install
Add Pagination from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/paginationThe same command installs the registry items it composes: @vegastack/icon-button.
Usage
import {
Pagination,
PaginationContent,
PaginationEllipsis,
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>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="?page=2" />
</PaginationItem>
</PaginationContent>
</Pagination>;Anatomy
Pagination is a compound component built from a presentational <nav> wrapping an unordered
list of page controls. Compose the parts inside the root — every part exposes a data-slot:
<Pagination aria-label="Search results pagination">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="?page=1" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="?page=1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="?page=2" isActive>
2 {/* current page */}
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis /> {/* collapse a long range */}
</PaginationItem>
<PaginationItem>
<PaginationNext href="?page=2" />
</PaginationItem>
</PaginationContent>
</Pagination>Pagination— the navigation landmark (data-slot="pagination"). Renders a plain<nav>—<nav>is the landmark, so no explicitrole— labelled "Pagination" unless you pass your ownaria-label.PaginationContent— the list of controls (<ul>,data-slot="pagination-content"). Flex-aligned with a consistent gap.PaginationItem— a single list slot (<li>,data-slot="pagination-item").PaginationLink— a navigable page link (<a>,data-slot="pagination-link"). Styled like a ghost/outline button; setisActiveon the current page and choose asize. Supports the Base UIrenderprop for client-side routing.PaginationPrevious— a labelled "previous page" control (<a>) with a leading chevron.PaginationNext— a labelled "next page" control (<a>) with a trailing chevron.PaginationEllipsis— a decorative collapsed-pages indicator (data-slot="pagination-ellipsis",aria-hidden) for long ranges.
Examples
Routing links
Use the render prop on PaginationLink (and PaginationPrevious/PaginationNext) to compose with
your router's link component while keeping pagination styling and semantics:
import Link from "next/link";
<PaginationLink render={<Link href="?page=2" />} isActive>
2
</PaginationLink>;Sizes
PaginationLink takes a size prop — sm (28px), md (32px), lg (40px), and icon (a 32px square,
the square default for numbered pages) — on the same 28/32/40 control scale as buttons and inputs. The
grid below renders all four: icon is the squared-off default, sm/md/lg widen the hit target
with horizontal padding.
Edge states
When a previous/next control is unavailable, omit href and set aria-disabled="true" — the link
then enforces the disabled state itself (removed from the tab order, clicks and Enter blocked). A
disabled link can never navigate.
On the first page, disable PaginationPrevious:
On the last page, mirror the pattern on PaginationNext:
Positional pager
PaginationPager is the compact record-pager: previous/next steppers + a "n of N [context]"
status label (bounds-disabled at the ends, tabular numerals). Use it for stepping through
items of a known list; keep Pagination for numbered page navigation.
Playground
Try every page-link size on a live pagination bar, then copy the generated JSX.
<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>
<PaginationLink href="?page=3">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="?page=3" />
</PaginationItem>
</PaginationContent>
</Pagination>API Reference
PaginationLink
| Prop | Type | Default | Description |
|---|---|---|---|
isActive | boolean | false | Marks the link as the current page — applies the active **primary**-fill
styling and sets aria-current="page". |
render | useRender.RenderProp<Record<string, unknown>> | — | Replace the rendered <a> element via Base UI render composition. Pass a
routing link element (e.g. <NextLink href="/x" />) or a render function to
integrate with a router while keeping pagination styling. |
size | "icon" | "lg" | "md" | "sm" | — |
PaginationPrevious / PaginationNext
PaginationPrevious and PaginationNext accept exactly the same props as
PaginationLink (isActive, size, render, plus native <a> props) — they
pre-compose the directional chevron and label.
Pagination, PaginationContent, PaginationItem, and PaginationEllipsis
add no props of their own — each accepts everything its underlying native
element accepts (<nav>, <ul>, <li>, <span>), plus className and
ref.
Accessibility
- The root is a navigation landmark: a plain
<nav aria-label="Pagination">.<nav>already carries the landmark role, so the component does not restate it. - Name every pager on a page that has more than one. Two landmarks with the same accessible name
are an axe
landmark-uniquefailure and give screen-reader users no way to tell them apart. Passaria-label="Search results pagination",aria-label="Invoices pagination", and so on — the default "Pagination" is correct only when the page has exactly one. - The active page uses
aria-current="page", so it is announced as the current location in the set. - Previous/Next controls carry an explicit
aria-label("Go to previous page" / "Go to next page") so the icon-only direction is still meaningful to screen readers. PaginationEllipsisis decorative (aria-hidden="true"). Use real page links or a separate labelled menu trigger when skipped pages need to be reachable.- Disabled Previous/Next controls omit
hrefand setaria-disabled— the component then removes them from the tab order and blocks activation itself (anaria-disabledanchor is otherwise not inert). - Links use the native
<a>and keep a visible:focus-visiblering — neveroutline: none.
| Key | Action |
|---|---|
| Tab | Move focus through the pagination controls. |
| Enter | Follow the focused page link. |
| Contract | States tested |
|---|---|
| Behaviour | default, active, collapsed, disabled |
| Accessibility | current, disabled, labeled, status-announcement, semantic-html |
| Visual | default, hover, active, disabled |