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
<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 attribute | What it does |
|---|---|
.luz-pagination | The list: one row of items, which wraps when it is narrow |
.luz-pagination | 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 | 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"andrel="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>witharia-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>, addrole="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” witharia-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
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.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
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-ringoutline 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 (
HighlightandHighlightText) and a focus ring inCanvasText; an end that is not a link isGrayText.
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.
- Color
--luz-brand-fill,--luz-surface-hover,--luz-surface-pressed,--luz-text-disabled,--luz-text-muted,--luz-text-on-fill,--luz-text-primary,--luz-text-secondary- 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.