Pagination
Page links for a list whose page is in the URL, with an optional summary ("Showing 1–20 of 248"). A navigation landmark with previous and next links, the page numbers with aria-current="page" on the current one, and gaps that keep the number of slots constant while you page.
Import
import { Pagination } from '@nizivo/react/pagination';
// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css
import '@nizivo/react/css/pagination.css';Static or interactive
Server. It ships no client JavaScript when a Server Component renders it, in every Next.js build
mode; its code is bundled only when a client component (a 'use client' file) imports it. It has
two modes:
- Links (
getHref): the page is in the URL,getHref(page)gives each page's URL, and the current page isaria-current="page".getHref, and a functionrenderLink, run where Pagination renders, so a Server Component passes them directly; a router link passed throughrenderLinkis the router's own code. - Buttons (
onPageChange), for client state with no URL: each page, previous and next is a<button type="button">, and choosing another page callsonPageChange(page, details)(details.reasonis'page','previous'or'next') with no navigation. The ends are disabled buttons. Pass it from a client component, for exampleonPageChange={setPage}. It adds no client module of its own. In a client app has a live demo.
Which pages show
The first and last boundaryCount pages always show, and so do siblingCount pages on each side of
the current one. A gap becomes "…" unless it would hide a single page, which shows instead. With the
defaults (1 and 1) that is 7 page slots once there are more than 7 pages, wherever the current page
is:
1 2 3 4 5 … 13, 1 … 6 7 8 … 13, 1 … 9 10 11 12 13. Figma's drawing (1 2 3 … 13) is
siblingCount={0}. The rule is internal (the tests cover it); it is not exported. With previous and next, the defaults take 9 slots: 392 px at size="md" (40 px squares)
and 320 px at sm (32 px). In a narrower container the slots wrap, so use sm and
siblingCount={0} there (7 slots, 248 px).
Examples
Basic
Figma's footer: a summary and 13 pages, page 1 current. Every page is a URL, so it is server-rendered and needs no JavaScript; previous is disabled on the first page.
import type { JSX } from 'react';
import { Pagination } from '@nizivo/react/pagination';
export function PaginationExample(): JSX.Element {
return (
<Pagination
page={1}
totalPages={13}
getHref={(page) => `?page=${page}`}
summary="Showing 1–20 of 248"
/>
);
}
Router
Mid-list with a router's link, the compact size and Figma's single-page siblings.
import type { ComponentPropsWithRef, JSX } from 'react';
import { Pagination } from '@nizivo/react/pagination';
/** Stands in for a router link (Next `Link`, React Router `Link`). */
function Link({ children, ...props }: ComponentPropsWithRef<'a'>): JSX.Element {
return <a {...props}>{children}</a>;
}
export function PaginationRouterExample(): JSX.Element {
return (
<Pagination
page={7}
totalPages={13}
siblingCount={0}
size="sm"
getHref={(page) => `/orders?page=${page}`}
renderLink={<Link />}
/>
);
}
Props
Server component Renders data-slot="pagination" with the class nz-pagination. Props: PaginationProps. Source: src/pagination/pagination.tsx.
| Name | Type | Default | Description |
|---|---|---|---|
boundaryCount | number | 1 | Pages always shown at each end. |
getHref | (page: number) => string | – | Link mode: the URL of a page, for example |
labels | Partial<PaginationLabels> | – | |
locale | string | 'en-US' | BCP 47 tag for the page numbers. |
nextIcon | ReactNode | – | |
onPageChange | (page: number, details: ChangeDetails<PaginationChangeReason>) => void | – | Button mode, for client state with no URL: the pages, previous and next are |
pagerequired | number | – | The page being shown, from 1. Clamped to |
previousIcon | ReactNode | – | Replaces the chevron of the previous link (mirrored under |
render | RenderProp<PaginationState> | – | Composition of the |
renderLink | RenderProp<PaginationLinkState> | – | The router's link for every page, previous and next link, for example |
siblingCount | number | 1 | Pages shown either side of the current one. |
size | PaginationSize"sm""md" | 'md' | With the defaults a long list is 9 slots: 392 px at |
summary | ReactNode | – | Text before the pages, for example "Showing 1–20 of 248" (Figma); you format it. |
totalPagesrequired | number | – | At least 1. |
- Also takes every attribute of
<nav>exceptchildren. (Omit<ComponentPropsWithRef<"nav">, "children">)
render takes an element, or, in client code, a function of the props and this state (PaginationState):
| Name | Type | Default | Description |
|---|---|---|---|
pagerequired | number | – | |
sizerequired | PaginationSize"sm""md" | – | |
totalPagesrequired | number | – |
Other exports
PaginationChangeReasontypeimport type { PaginationChangeReason } from '@nizivo/react/pagination'Which control asked for the page.
type PaginationChangeReason = "page" | "previous" | "next"PaginationLabelstypeimport type { PaginationLabels } from '@nizivo/react/pagination'Pagination's own strings (§16.4): serialisable, English defaults;
{page}is replaced.Name Type Default Description navrequiredstring'Pagination'The landmark's name.
nextrequiredstring'Next page'pagerequiredstring'Page {page}'Each page link's name; the visible number stays in it (WCAG 2.5.3).
previousrequiredstring'Previous page'The previous link's name (it shows only a chevron).
PaginationLinkStatetypeimport type { PaginationLinkState } from '@nizivo/react/pagination'Name Type Default Description currentrequiredboolean– kindrequired"page" | "previous" | "next"– pagerequirednumber– The page this link goes to.
PaginationPropstypeimport type { PaginationProps } from '@nizivo/react/pagination'The props of
Pagination, listed above.PaginationSizetypeimport type { PaginationSize } from '@nizivo/react/pagination'40 px (
md) or 32 px (sm) squares; Figma's 36 px is off the control scale (figma/06).type PaginationSize = "sm" | "md"PaginationSlottypeimport type { PaginationSlot } from '@nizivo/react/pagination'One slot of the list: a page number or a gap.
type PaginationSlot = number | "ellipsis"PaginationStatetypeimport type { PaginationState } from '@nizivo/react/pagination'The render state of
Pagination, listed above.
Data attributes and slots
Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.
| Slot | Rendered by | Data attributes |
|---|---|---|
pagination.nz-pagination | Paginationroot (takes render) |
|
pagination-ellipsis.nz-pagination-ellipsis | Paginationinner element (CSS only) | none |
pagination-icon.nz-pagination-icon | Paginationinner element (CSS only) |
|
pagination-list.nz-pagination-list | Paginationinner element (CSS only) | none |
pagination-next.nz-pagination-next | Paginationinner element (CSS only); interactive, focus: self | none |
pagination-page.nz-pagination-page | Paginationinner element (CSS only); interactive, focus: self; forced colours: highlight on [aria-current='page'] | none |
pagination-previous.nz-pagination-previous | Paginationinner element (CSS only); interactive, focus: self | none |
pagination-summary.nz-pagination-summary | Paginationinner element (CSS only) | none |
CSS hooks
Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.
| Hook | Default | Read as |
|---|---|---|
--nz-pagination-bg-current | --nz-bg-inverse |
|
--nz-pagination-bg-hover | --nz-bg-hover |
|
--nz-pagination-fg | --nz-text-secondary |
|
--nz-pagination-fg-current | --nz-text-inverse |
|
--nz-pagination-gap | --nz-space-1 |
|
--nz-pagination-radius | --nz-radius-sm |
|
--nz-pagination-step-border | --nz-border-default |
|
--nz-pagination-step-fg | --nz-icon-secondary |
|
--nz-pagination-summary-fg | --nz-text-secondary |
|
Layers: nz.components. Classes: .nz-glyph, .nz-pagination, .nz-pagination-ellipsis, .nz-pagination-icon, .nz-pagination-list, .nz-pagination-page, .nz-pagination-previous, .nz-pagination-step, .nz-pagination-summary.
Tokens this stylesheet reads (21)
--nz-bg-hover, --nz-bg-inverse, --nz-border-default, --nz-border-width-default, --nz-duration-fast, --nz-easing-standard, --nz-font-body-m, --nz-font-label-m, --nz-icon-secondary, --nz-radius-sm, --nz-size-control-md, --nz-size-control-sm, --nz-size-icon-sm, --nz-space-1, --nz-space-2, --nz-space-4, --nz-text-disabled, --nz-text-inverse, --nz-text-primary, --nz-text-secondary, --nz-tracking-label-m
Accessibility
These parts are interactive. Each gets the focus outline and touch-action rule from @nizivo/react/css/base.css (the nz.base layer), so the floor survives a restyle:
pagination-next: draws the focus outline itself on keyboard focus.pagination-page: draws the focus outline itself on keyboard focus.pagination-previous: draws the focus outline itself on keyboard focus.
In forced-colours mode these states keep a visible system colour: pagination-page[aria-current='page'] (highlight).
Keyboard
| Keys | Action |
|---|---|
| Tab, Shift+Tab | Moves between the links, as on any link. A previous or next link at the ends is disabled and not a Tab stop. |
| Enter | Follows the focused link. |
Source: native <a> elements in a <nav> (pagination.tsx, no client code). The browser lane (test/browser/pagination.spec.tsx) checks that on page 1 Tab skips the disabled previous link, visits every page link, then next, and the focus outline.
Figma
Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.
- Canonical: Figma node
755:1007 - Ghost masters: none
PageIndicator
Where a carousel, a product's photos or an onboarding sequence is, as dots or as a "2 / 9" chip. The visual is decorative; a visually hidden "2 of 9" tells a screen reader the same.
PasswordInput
A native password input in Input's box, with a toggle that shows the password. The toggle keeps one name, "Show password", and says whether the password shows through its pressed state; a submit hides the password again.