Skip to main content

Core · Navigation

Pagination

Links to the pages of a list that goes on over several, with the current page marked.

Example

The links of a list of 40 pages, with the current page marked.

Known issues

None known.

Code

Copy the markup

HTML
<nav aria-label="Pagination">
  <ul class="luz-pagination">
    <li><a class="luz-pagination__link" href="/blog/?page=1" rel="prev">Previous</a></li>
    <li><a class="luz-pagination__link" href="/blog/?page=1" aria-label="Page 1">1</a></li>
    <li><a class="luz-pagination__link" href="/blog/?page=2" aria-label="Page 2" aria-current="page">2</a></li>
    <li><a class="luz-pagination__link" href="/blog/?page=3" aria-label="Page 3">3</a></li>
    <li><a class="luz-pagination__link" href="/blog/?page=3" rel="next">Next</a></li>
  </ul>
</nav>

Variants and options

There are no modifiers. The list wraps onto more lines when it is narrow.

Class or attributeWhat it does
.luz-pagination
The list: one row of items, which wraps when it is narrow
.luz-pagination__link
A page, Previous or Next: a target of at least 44 px
aria-current="page"
Marks the current page: solid fill and heavier weight
aria-disabled="true"
Previous or Next at either end, as a <span> and not a link: disabled text colour
.luz-pagination__gap
The “…” of a long list: a mark, hidden from screen readers

How it works

A <nav> landmark around a list of links, styled by components/pagination.css. Each page is a real address, so the server renders it and the back button, a bookmark and a search engine all work.

No JavaScript.

  • The numbers are links, not buttons: following one loads another page.
  • rel="prev" and rel="next" say which links go to the neighbours.
  • The current page is a link with aria-current="page", so it is still announced as a link and keeps its place in the order.
  • At the first or last page, Previous or Next is a <span> with aria-disabled="true": text, with no focus stop.
  • Keep the <nav>. Some browsers drop the list semantics of a <ul> without bullets, and the landmark is what keeps it announced as a list; without a <nav>, add role="list" to the <ul>.

Without JavaScript

Nothing changes: it is a list of links and CSS. Without CSS it reads as a list of links.

Keyboard and ARIA

Tab reaches each link in order; Enter follows it.

  • Name the <nav> “Pagination” with aria-label, so it differs from the main navigation.
  • Give a number the name “Page 3” with aria-label. The visible number stays in the name. aria-current="page" makes the screen reader say which one is current, so the name does not need to.
  • A gap is aria-hidden="true": the screen reader hears the pages, not the dots.

Contrast and focus are in the Design tab.

Design

When to use

Use pagination when a list is too long for one page and each page can be reached by its own address: search results, a blog archive, a table of records.

When not to use

Do not use pagination for a short list, or for the steps of a task.

  • For the parents of the page you are on, use a breadcrumb.
  • For the sections of one page, use tabs.
  • For a feed that keeps loading on scroll, give a link to the next page as well: it is the only version a keyboard and a screen reader reach.

Do and don't

Do Show the first and last page, the current one and its neighbours, and a gap between.
Don't Print every page of a long list: it grows past the width and the reader has to count.

Writing the content

Use the word the reader would say.

  • “Previous” and “Next”, not arrows alone and not “Back”.
  • Page numbers as plain digits; name them “Page 3” for assistive technology.
  • Say how many pages there are near the list (“Page 8 of 40”) when the total matters.

Contrast and focus

Pages are secondary text; the current page is white on a solid brand fill.

PartColour on backgroundLightDarkHigh contrastNeeds
Link
--luz-text-secondary on --luz-surface-page
6.2:1
7.5:1
19.3:1
4.5:1
Link on hover
--luz-text-primary on --luz-surface-hover
14.4:1
12.6:1
20.1:1
4.5:1
Current page
--luz-text-on-fill on --luz-brand-fill
6.0:1
4.9:1
14.1:1
4.5:1
Current page fill
--luz-brand-fill on --luz-surface-page
5.6:1
3.5:1
14.1:1
3:1
Previous or Next at the end
--luz-text-disabled on --luz-surface-page
3.5:1
4.2:1
5.3:1
3:1
Gap
--luz-text-muted on --luz-surface-page
4.9:1
5.6:1
9.0:1
4.5:1
Focus ring
--luz-focus-ring on --luz-surface-page
5.6:1
7.7:1
21.0:1
3:1

Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders, icons and the disabled state.

  • Focus: each link has the shared link ring, a 2 px --luz-focus-ring outline 2 px outside the item.
  • Not colour alone: the current page has a solid fill and a heavier weight (600), and carries aria-current.
  • Forced colors: the current page takes the system selection colors (Highlight and HighlightText) and a focus ring in CanvasText; an end that is not a link is GrayText.

Tokens it uses

The 15 tokens that components/pagination.css reads, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.

Spacing and sizing
--luz-control-lg, --luz-space-sm, --luz-space-xs
Typography
--luz-font-sans, --luz-text-md
Radius
--luz-radius-md
Motion
--luz-duration-fast

Keyboard and ARIA are in the Code tab.