Skip to main content

Design system components

Pagination

Pagination controls for paged lists (article archive, search results). Renders a <nav> landmark with previous/next buttons and a row of numbered page buttons (buttons, not links, so pages have no crawlable href); supports ellipsis truncation for long page ranges.

Status: stable. Group: navigation. Markdown version: /design-system/components/pagination.md

When to use

Pagination is the controlled pager for content collections (blog listings, search results). Hold the page in state or the URL, pass it as currentPage (1-indexed), and re-render from onPageChange. It shows first/last pages plus a siblingCount window around the current page with ellipsis in between, previous/next chevrons at the ends, and aria-current="page" on the active number. Labels come from i18next (en/fi/sv). With one page or fewer it renders nothing.

When not to use

  • Do not remove Previous/Next on edges; keep them as disabled buttons so the visual stays stable.
  • Do not render more than ~10 page numbers inline; switch to a 'Page X of Y' label with prev/next only.
  • Do not use it for wizard steps; steps need progress semantics, not a content pager.

Look-alikes

The contract names no look-alike with a deciding condition.

Props

Pagination props
PropTypeDefaultDescriptionRequired
currentPagenumbernoneCurrent active page (1-indexed)Yes
totalPagesnumbernoneTotal number of pages.Yes
onPageChange(page: number) => voidnoneCallback when page changes.Yes
siblingCountnumber1Number of page buttons to show on each side of current page.No
labelstringnoneAccessible name of the navigation landmark, for pages with several navs (for example "Questionnaire pages"). Defaults to the localized "Page navigation".No
classNamestringnoneCustom className.No

Variants

No variant dimensions and no props with a fixed set of values.

Invalid combinations

The contract records no invalid combinations.

Keyboard

Pagination keyboard behaviour
KeyBehaviour
TabMoves through the page links and prev/next buttons in document order.
Enter / SpaceActivates the focused control.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

Pagination rules
LevelRuleHow it is checked
MUSTARIA: Renders a <nav> landmark labelled from i18n (blogPageNavigation → 'Page navigation' / 'Sivunavigointi' / 'Sidnavigering').Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Current page sets aria-current='page'.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Previous/next are <button disabled> on the edges (still in the DOM) so the visual structure does not jump.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Chevrons and the ellipsis are aria-hidden; each page button carries its own Page N accessible name.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDTreat it as fully controlled: update state (or the URL) in onPageChange and pass the new currentPage back down.Not stated in the contract.
SHOULDSync the page with the URL (searchParams) so paginated views deep-link and survive refresh.Not stated in the contract.
SHOULDTune siblingCount to available width; the default 1 keeps the row compact on mobile.Not stated in the contract.
SHOULDLet it self-hide: it renders null when totalPages <= 1, so no outer conditional is needed.Not stated in the contract.
SHOULD NOTDo not fetch on click without updating currentPage; the active highlight and aria-current derive from props alone.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { Pagination } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/navigation exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-pagination>; see the custom elements manifest.