Skip to content
Nizivo UI

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.

Server component, no client JS

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 is aria-current="page". getHref, and a function renderLink, run where Pagination renders, so a Server Component passes them directly; a router link passed through renderLink is 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 calls onPageChange(page, details) (details.reason is 'page', 'previous' or 'next') with no navigation. The ends are disabled buttons. Pass it from a client component, for example onPageChange={setPage}. It adds no client module of its own. In a client app has a live demo.

How to choose.

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.

NameTypeDefaultDescription
boundaryCountnumber1

Pages always shown at each end.

getHref(page: number) => string–

Link mode: the URL of a page, for example ?page=3 for page 3. Give it or onPageChange.

labelsPartial<PaginationLabels>–
localestring'en-US'

BCP 47 tag for the page numbers.

nextIconReactNode–
onPageChange(page: number, details: ChangeDetails<PaginationChangeReason>) => void–

Button mode, for client state with no URL: the pages, previous and next are <button type="button">s, and choosing another page calls this with it (the current page calls nothing). getHref and renderLink are not used. A client component passes it.

pagerequirednumber–

The page being shown, from 1. Clamped to 1…totalPages.

previousIconReactNode–

Replaces the chevron of the previous link (mirrored under dir="rtl" only by default).

renderRenderProp<PaginationState>–

Composition of the <nav> landmark (a single-root part's render, §8.2).

renderLinkRenderProp<PaginationLinkState>–

The router's link for every page, previous and next link, for example renderLink={(props) => <Link {...props} href={String(props['href'])} />}, or an element such as renderLink={<RouterLink />}, which receives href from getHref. It must render an <a>. A previous or next link at the ends is not a link and does not use it. Link mode only.

siblingCountnumber1

Pages shown either side of the current one.

sizePaginationSize
"sm""md"
'md'

With the defaults a long list is 9 slots: 392 px at md, 320 px at sm. In a narrower container the slots wrap, so use sm and siblingCount={0} (7 slots, 248 px) there.

summaryReactNode–

Text before the pages, for example "Showing 1–20 of 248" (Figma); you format it.

totalPagesrequirednumber–

At least 1.

  • Also takes every attribute of <nav> except children. (Omit<ComponentPropsWithRef<"nav">, "children">)

render takes an element, or, in client code, a function of the props and this state (PaginationState):

NameTypeDefaultDescription
pagerequirednumber–
sizerequiredPaginationSize
"sm""md"
–
totalPagesrequirednumber–

Other exports

PaginationChangeReason type

import type { PaginationChangeReason } from '@nizivo/react/pagination'

Which control asked for the page.

type PaginationChangeReason = "page" | "previous" | "next"

PaginationLabels type

import type { PaginationLabels } from '@nizivo/react/pagination'

Pagination's own strings (§16.4): serialisable, English defaults; {page} is replaced.

NameTypeDefaultDescription
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).

PaginationLinkState type

import type { PaginationLinkState } from '@nizivo/react/pagination'

NameTypeDefaultDescription
currentrequiredboolean–
kindrequired"page" | "previous" | "next"–
pagerequirednumber–

The page this link goes to.

PaginationProps type

import type { PaginationProps } from '@nizivo/react/pagination'

The props of Pagination, listed above.

PaginationSize type

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

PaginationSlot type

import type { PaginationSlot } from '@nizivo/react/pagination'

One slot of the list: a page number or a gap.

type PaginationSlot = number | "ellipsis"

PaginationState type

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

SlotRendered byData attributes
pagination
.nz-pagination
Pagination
root (takes render)
  • data-size: "sm""md" ← size
    The size prop; values in each component manifest
pagination-ellipsis
.nz-pagination-ellipsis
Pagination
inner element (CSS only)
none
pagination-icon
.nz-pagination-icon
Pagination
inner element (CSS only)
  • data-mirror: "rtl"
    Mirror under :dir(rtl)
pagination-list
.nz-pagination-list
Pagination
inner element (CSS only)
none
pagination-next
.nz-pagination-next
Pagination
inner element (CSS only); interactive, focus: self
none
pagination-page
.nz-pagination-page
Pagination
inner element (CSS only); interactive, focus: self; forced colours: highlight on [aria-current='page']
none
pagination-previous
.nz-pagination-previous
Pagination
inner element (CSS only); interactive, focus: self
none
pagination-summary
.nz-pagination-summary
Pagination
inner 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.

HookDefaultRead as
--nz-pagination-bg-current--nz-bg-inverse
  • background on .nz-pagination-page[aria-current="page"]
--nz-pagination-bg-hover--nz-bg-hover
  • background on .nz-pagination-page:not([aria-current="page"]):hover @media (hover: hover)
  • background on .nz-pagination-step:not([data-disabled]):hover @media (hover: hover)
--nz-pagination-fg--nz-text-secondary
  • color on .nz-pagination-ellipsis
  • color on .nz-pagination-page
  • color on .nz-pagination-step
--nz-pagination-fg-current--nz-text-inverse
  • color on .nz-pagination-page[aria-current="page"]
--nz-pagination-gap--nz-space-1
  • gap on .nz-pagination-list
--nz-pagination-radius--nz-radius-sm
  • border-radius on .nz-pagination-ellipsis
  • border-radius on .nz-pagination-page
  • border-radius on .nz-pagination-step
--nz-pagination-step-border--nz-border-default
  • border-color on .nz-pagination-step
--nz-pagination-step-fg--nz-icon-secondary
  • color on .nz-pagination-step
--nz-pagination-summary-fg--nz-text-secondary
  • color on .nz-pagination-summary

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

KeysAction
Tab, Shift+TabMoves between the links, as on any link. A previous or next link at the ends is disabled and not a Tab stop.
EnterFollows 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.

On this page