Your pagination is probably not accessible
Tags: webdev, tutorial, html, a11y
Published: 2023-08-31 14:28:01

Pagination is a feature widely implemented in several visual interfaces that require listing data (like blogs, social media, and e-commerce) since it provides a better user experience and prevents the need to fetch all data from the server at once (imagine that!). But are you doing it right?

## Table of contents
1. [What do I mean by "right"?](#what-do-i-mean-by-right)
2. [Initial code](#initial-code)
3. [Why not buttons?](#why-not-buttons)
4. [Final code](#final-code)
5. [Conclusion](#conclusion)
6. [References](#references)

## What do I mean by "right"?

By "right" I mean "to include all users on the web." Or are you just stacking pretty links with numbers? Or even worse: stacking buttons (we will discuss that)!

## Initial code

In order to understand what is happening, let's start with a very basic version of the component, and over the article we can improve it. In this tutorial, for didactic purposes, we are going to use only HTML and CSS. No need to render the number of pages dynamically or anything like that.

```html
<nav>
  <ul class="pagination__list">
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">Previous</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">1</a>
    </li>
    <li>
      <a class="pagination__ellipsis">&#8230;</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">6</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">
        7
      </a>
    </li>
    <li class="pagination__list-item pagination__list-item--current">
      <a href="#" class="pagination__anchor">8</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">9</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">10</a>
    </li>
    <li>
      <a class="pagination__ellipsis">&#8230;</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">20</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">Next</a>
    </li>
  </ul>
</nav>
```
This version is a good start, actually. At least we are using semantic html correctly, explicitly saying it's a nav component, and listing the links properly. But this is far from accessible: **people using assistive technologies, such as screen readers, will need to guess several things by context!**

## Why not buttons?

Simply put: **don't frustrate your users.** Someone navigating on your website expects elements to behave as they should.

A link will redirect you to a new page or section within the same page, and a button will trigger an action.

Both are focusable, but people interacting through the keyboard trigger links with the `enter` key, and buttons with the `space` key.

There are interesting discussions about it regarding filters that change router parameters, though. But we will keep it out of this article.

And please, no `div` tags with event listeners. It requires a lot of code, it harms the readability of other developers and it's not semantic at all!

## Final code
First, let's break down the improvements you will see below:

- We added an `aria-label="pagination"` because that's most likely not the only navigation component on the page, **so we should explicitly differentiate it from the main nav**.
- Pay attention to the `.visually-hidden` class. **We use it in span tags to provide more context for people using screen readers** while hiding it from the visual user interface. It is implemented in accessibility-focused component libraries like [Radix-ui](https://www.radix-ui.com/primitives/docs/utilities/visually-hidden).
- For the current page, we add `aria-current="page"` as an indicator of the active link. The `aria-label="page"` provides the same additional context as the visually-hidden span.

```html
<nav aria-label="pagination">
  <ul class="pagination__list">
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">Previous <span class="visually-hidden">page</span></a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page"><span class="visually-hidden">page</span> 1 <span class="visually-hidden">(first page)</span></a>
    </li>
    <li>
      <a class="pagination__ellipsis">&#8230;</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page"><span class="visually-hidden">page</span> 6</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">
        <span class="visually-hidden">page</span> 7
      </a>
    </li>
    <li class="pagination__list-item pagination__list-item--current">
      <a href="#" class="pagination__anchor" aria-label="page 8" aria-current="page">8</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page"><span class="visually-hidden">page</span> 9</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page"><span class="visually-hidden">page</span> 10</a>
    </li>
    <li>
      <a class="pagination__ellipsis">&#8230;</a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page"><span class="visually-hidden">page</span> 20 <span class="visually-hidden">(last page)</span></a>
    </li>
    <li class="pagination__list-item">
      <a class="pagination__anchor" href="path/to/page">Next <span class="visually-hidden">page</span></a>
    </li>
  </ul>
</nav>
```
Here is the full CSS:

```css
/* Small reset */
body {
  box-sizing: border-box;
}

* {
  padding: 0;
  margin: 0;
  list-style-type: none;
}

.pagination__list {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 20px;
  gap: 5px;
}

.pagination__list-item {
  background-color: #4682b4;
  padding: 10px;
  border-radius: 5px;
  cursor: pointer;
}

.pagination__list-item--current {
  background-color: #191970;
}

.pagination__anchor {
  text-decoration: none;
  color: #ffffff;
}

.visually-hidden {
  border: 0;
  padding: 0;
  margin: 0;
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  clip-path: inset(50%);
  white-space: nowrap;
}
```

You can also check it out on code sandbox:

{% codesandbox thirsty-hypatia-h6dx3n %}

## Conclusion

Keep in mind that there is always room for improvement. There are different assistive technologies, different disabilities, and different ways to interact with websites. Our main goal here is to start thinking about best practices and to build a better web for everyone, step-by-step.

Please, feel free to contribute in the comments. We are all learning!

Thank you for reading.

## References
1. [https://design-system.w3.org/components/pagination.html](https://design-system.w3.org/components/pagination.html)
2. [https://dev.to/grahamthedev/101-digital-accessibility-tips-and-tricks-4728](https://dev.to/grahamthedev/101-digital-accessibility-tips-and-tricks-4728)
3. [https://a11y-101.com/design/button-vs-link](https://a11y-101.com/design/button-vs-link)