Pagination

nav.pagination

Pagination — links to the pages of a paged result.

Examples

A range with an elision

View “A range with an elision” as
<nav class="pagination" aria-label="Pagination">
  <ol class="pagination-list">
    <li>
      <a class="btn" href="#page-3" rel="prev">
        <svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg>
        Previous
      </a>
    </li>
    <li><a class="btn btn-quiet pagination-page" href="#page-1">1</a></li>
    <li aria-hidden="true" class="pagination-ellipsis">&hellip;</li>
    <li><a class="btn btn-quiet pagination-page" href="#page-3">3</a></li>
    <li><a class="btn btn-quiet pagination-page" href="#page-4" aria-current="page">4</a></li>
    <li><a class="btn btn-quiet pagination-page" href="#page-5">5</a></li>
    <li>
      <a class="btn" href="#page-5" rel="next">
        Next
        <svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg>
      </a>
    </li>
  </ol>
</nav>

Every control is a .btn, so the touch floor, the focus ring and the disabled treatment are the button’s and cannot drift.

.pagination-page is only there to make a number square — a .btn is sized by its label, so "1" and "10" would differ.

At the first page

View “At the first page” as
<nav class="pagination" aria-label="Pagination">
  <ol class="pagination-list">
    <li>
      <button class="btn" type="button" disabled>
        <svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg>
        Previous
      </button>
    </li>
    <li><a class="btn btn-quiet pagination-page" href="#page-1" aria-current="page">1</a></li>
    <li><a class="btn btn-quiet pagination-page" href="#page-2">2</a></li>
    <li>
      <a class="btn" href="#page-2" rel="next">
        Next
        <svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg>
      </a>
    </li>
  </ol>
</nav>

The dead end is a <button disabled>, not an <a aria-disabled="true">. Without JavaScript the second one still navigates: it announces a state it does not have.

Icon-only previous and next

View “Icon-only previous and next” as
<nav class="pagination" aria-label="Search results pages">
  <ol class="pagination-list">
    <li>
      <a class="btn btn-icon" href="#page-3" rel="prev" aria-label="Previous page">
        <svg class="icon" aria-hidden="true"><use href="#icon-chevron-left"></use></svg>
      </a>
    </li>
    <li><a class="btn btn-quiet pagination-page" href="#page-1">1</a></li>
    <li aria-hidden="true" class="pagination-ellipsis">&hellip;</li>
    <li><a class="btn btn-quiet pagination-page" href="#page-3">3</a></li>
    <li><a class="btn btn-quiet pagination-page" href="#page-4" aria-current="page">4</a></li>
    <li><a class="btn btn-quiet pagination-page" href="#page-5">5</a></li>
    <li aria-hidden="true" class="pagination-ellipsis">&hellip;</li>
    <li><a class="btn btn-quiet pagination-page" href="#page-24">24</a></li>
    <li>
      <a class="btn btn-icon" href="#page-5" rel="next" aria-label="Next page">
        <svg class="icon" aria-hidden="true"><use href="#icon-chevron-right"></use></svg>
      </a>
    </li>
  </ol>
</nav>

No new class: dropping the words is .btn-icon, which the button already ships, and the whole variant is two aria-labels.

Those labels are not optional and nothing detects their absence — an icon-only control with no name announces as "link", and a pagination bar of two anonymous links and eight numbers is unusable by anyone listening. The name goes on the control, never on the <svg>: an aria-label on a decorative graphic is ignored, and one on a <use> is ignored twice over. Chevrons alone are the right choice on a narrow layout and the wrong one on a wide search-results page, where "Previous" and "Next" are both clearer and larger targets.

Tokens 7

Level 2, declared on .pagination itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by pagination
TokenDefault
--pagination-cell-size2.5rem
--pagination-current-bgvar(--color-primary)
--pagination-current-colorvar(--color-on-primary)
--pagination-current-font-weightvar(--font-weight-semibold)
--pagination-ellipsis-colorvar(--color-on-surface-muted)
--pagination-ellipsis-font-sizevar(--font-size-sm)
--pagination-gapvar(--space-1)

Variants and states

Variants

None. It is one shape, and the page does the rest.

Inside it

  • .pagination-page
  • .pagination-ellipsis
  • .pagination-list

State it reads

None.

Before you ship it

What you have to do 1 requirements

  1. Name the nav: aria-label="Pagination". A page that also has a primary nav otherwise announces two identical landmarks. Mark the current page with aria-current="page" on its anchor, and mark exactly one. It is the accessible signal as well as the visual one, and this file reads it straight from the platform — there is no .is-current class to keep in sync, deliberately. Keep the ellipsis aria-hidden="true". It is a typographic hint that some numbers were left out; announced, it is a stray "…" between two links. Give the previous/next controls a name that survives out of context if you replace their text with an icon: the label goes on the control (aria-label="Previous page"), never on the <svg>, which is aria-hidden. rel="prev" and rel="next" are worth adding and are not styled here.

src/css/components/pagination.css · npx mostlycss add pagination