Accessibility pattern · Navigation

Pagination

Each link says where it goes: "Previous page", "Page 4", "Next page", and the current one is marked with aria-current="page". When the list updates in place, a status line says "Page 4 of 12" and focus stays on the links, so nobody has to find their way back.

WCAG criteria
6
Keyboard rules
2
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Your orders

Page 1 of 12

  • Brass diya set #40260 ₹649 Processing
  • Copper water bottle #40253 ₹899 Shipped
  • Sandalwood soap, pack of 3 #40246 ₹420 Shipped
  • Bamboo table lamp #40239 ₹1,180 Delivered

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Tab or ShiftTabMoves through Previous, the page numbers and Next; the ellipses are not links and are skipped.
EnterFollows the focused link: the list shows that page, the status line says which page it is, and focus stays on the links.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Tab reaches the current pageOrder pages, navigation. List. Page 1, current page, link
Tab reaches the last linkNext page, link
Enter on Page 4Page 4 of 12
Focus after the list changesPage 4, current page, link

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-pagination" data-ap-pagination data-pages="12">
  <div class="ap-pagination__top">
    <h3 class="ap-pagination__name" id="pg-name">Your orders</h3>
    <p class="ap-pagination__status" role="status"><span>Page</span> <span>1</span> <span>of</span> <span>12</span></p>
  </div>
  <ul class="ap-pagination__orders" aria-labelledby="pg-name">
    <li class="ap-pagination__order">
      <span class="ap-pagination__thumb" aria-hidden="true"><svg class="ap-pagination__box" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z"/><path d="m4 7.5 8 4.5 8-4.5M12 12v9"/></svg></span>
      <span class="ap-pagination__product">Brass diya set</span>
      <span class="ap-pagination__no" translate="no">#40260</span>
      <span class="ap-pagination__amount">₹649</span>
      <span class="ap-pagination__state ap-pagination__state--processing"><svg class="ap-pagination__state-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 2"/></svg><span>Processing</span></span>
    </li>
    <li class="ap-pagination__order">
      <span class="ap-pagination__thumb" aria-hidden="true"><svg class="ap-pagination__box" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z"/><path d="m4 7.5 8 4.5 8-4.5M12 12v9"/></svg></span>
      <span class="ap-pagination__product">Copper water bottle</span>
      <span class="ap-pagination__no" translate="no">#40253</span>
      <span class="ap-pagination__amount">₹899</span>
      <span class="ap-pagination__state ap-pagination__state--shipped"><svg class="ap-pagination__state-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 7h11v9H3z"/><path d="M14 10h4l3 3v3h-7"/><circle cx="7" cy="17.5" r="1.5"/><circle cx="17" cy="17.5" r="1.5"/></svg><span>Shipped</span></span>
    </li>
    <li class="ap-pagination__order">
      <span class="ap-pagination__thumb" aria-hidden="true"><svg class="ap-pagination__box" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z"/><path d="m4 7.5 8 4.5 8-4.5M12 12v9"/></svg></span>
      <span class="ap-pagination__product">Sandalwood soap, pack of 3</span>
      <span class="ap-pagination__no" translate="no">#40246</span>
      <span class="ap-pagination__amount">₹420</span>
      <span class="ap-pagination__state ap-pagination__state--shipped"><svg class="ap-pagination__state-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 7h11v9H3z"/><path d="M14 10h4l3 3v3h-7"/><circle cx="7" cy="17.5" r="1.5"/><circle cx="17" cy="17.5" r="1.5"/></svg><span>Shipped</span></span>
    </li>
    <li class="ap-pagination__order">
      <span class="ap-pagination__thumb" aria-hidden="true"><svg class="ap-pagination__box" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z"/><path d="m4 7.5 8 4.5 8-4.5M12 12v9"/></svg></span>
      <span class="ap-pagination__product">Bamboo table lamp</span>
      <span class="ap-pagination__no" translate="no">#40239</span>
      <span class="ap-pagination__amount">₹1,180</span>
      <span class="ap-pagination__state ap-pagination__state--delivered"><svg class="ap-pagination__state-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg><span>Delivered</span></span>
    </li>
  </ul>
  <nav class="ap-pagination__nav" aria-label="Order pages">
    <ul class="ap-pagination__pages">
      <li class="ap-pagination__edge ap-pagination__edge--prev ap-pagination__edge--off"><a class="ap-pagination__step" href="?page=1" data-step="prev" rel="prev"><svg class="ap-pagination__arrow" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m15 18-6-6 6-6"/></svg><span>Previous</span><span class="ap-pagination__vh"> page</span></a></li>
      <li class="ap-pagination__num"><a class="ap-pagination__page" href="?page=1" aria-current="page"><span class="ap-pagination__vh">Page </span>1</a></li>
      <li class="ap-pagination__num"><a class="ap-pagination__page" href="?page=2"><span class="ap-pagination__vh">Page </span>2</a></li>
      <li class="ap-pagination__num"><a class="ap-pagination__page" href="?page=3"><span class="ap-pagination__vh">Page </span>3</a></li>
      <li class="ap-pagination__num"><a class="ap-pagination__page" href="?page=4"><span class="ap-pagination__vh">Page </span>4</a></li>
      <li class="ap-pagination__num"><a class="ap-pagination__page" href="?page=5"><span class="ap-pagination__vh">Page </span>5</a></li>
      <li class="ap-pagination__gap" aria-hidden="true">…</li>
      <li class="ap-pagination__num"><a class="ap-pagination__page" href="?page=12"><span class="ap-pagination__vh">Page </span>12</a></li>
      <li class="ap-pagination__count" aria-hidden="true"><span>1</span><span class="ap-pagination__of">/</span><span>12</span></li>
      <li class="ap-pagination__edge ap-pagination__edge--next"><a class="ap-pagination__step" href="?page=2" data-step="next" rel="next"><span>Next</span><span class="ap-pagination__vh"> page</span><svg class="ap-pagination__arrow" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m9 18 6-6-6-6"/></svg></a></li>
    </ul>
  </nav>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    The links sit in a list inside a nav landmark named for what it pages through, so people can jump to it and hear how many links it holds.

  • 1.4.1 Use of Color Level A

    The current page is a filled, bold square, not just a different color, and the ellipses are plain text, not link-colored.

  • 2.4.4 Link Purpose (In Context) Level A

    Visually hidden words turn "4" into "Page 4" and the arrows into "Previous page" and "Next page", so each link makes sense on its own.

  • 2.5.8 Target Size (Minimum) Level AA

    Every link is at least 44 by 44 pixels, with space between, so the numbers are easy to hit on a phone.

  • 4.1.2 Name, Role, Value Level A

    aria-current="page" exposes which page is showing; screen readers say "current page".

  • 4.1.3 Status Messages Level AA

    The status line announces "Page 4 of 12" when the list changes in place, without moving focus.

Usage

When to use it

Use it

  • Long lists people work through in order: orders, search results, archives.
  • Results people come back to or share, because page 4 has its own address.

Use something else

  • Feeds people browse rather than search: a load more button keeps their place better.
  • Lists short enough to show on one page.
  • Steps of a task that must be done in order: use progress steps.

Common failures

How it usually goes wrong

  • Numbers with no name

    A link called just "4" means little in a screen reader's list of links. A visually hidden "Page" makes it "Page 4" without changing the look.

  • Arrows instead of words

    « and » are read as punctuation names, or not at all. Previous and Next are words here, with "page" added for screen readers.

  • Buttons instead of links

    Page buttons cannot be opened in a new tab, bookmarked or shared, and do nothing without the script. These are real links to ?page=4, enhanced to update in place.

  • A silent update

    When the list changes and nothing is said, a screen reader user presses Enter and hears nothing. The status line says which page is showing.

  • Focus lost when the links redraw

    Rebuilding the links removes the one that had focus, and focus falls to the top of the page. The script puts it on the new current page, or back on Previous or Next.

  • A disabled Previous link

    A link cannot be disabled; one styled grey but still focusable leads nowhere. On the first page Previous is hidden with visibility: hidden, which also takes it out of the Tab order, and its space is kept so nothing moves.

Notes

Building it

  • If every page is a full page load, the browser moves focus and announces the new page itself; the status line and the focus handling here are for lists that update in place.
  • Keep the hrefs real and update the address with history.pushState in a real app, so the back button and shared links work. The demo leaves this page's address alone.
  • Name the nav for what it pages through, like Order pages; a plain Pagination label is common too, but give each one its own name if a page has two.
  • Ctrl-click, middle-click and the like are left to the browser, so a page can still be opened in a new tab.
  • When the component is narrower than about 560 pixels the numbers give way to a count between Previous and Next, so the row never wraps onto two lines.

Sources: GOV.UK Design System: Pagination · WAI-ARIA 1.2: aria-current

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction