gabrielprrd

Your pagination is probably not accessible

4 min read

    Tags:

    webdev

    tutorial

    html

    a11y

Blog post cover

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"?
  2. Initial code
  3. Why not buttons?
  4. Final code
  5. Conclusion
  6. 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.

<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.
  • 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.
<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:

/* 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
  2. https://dev.to/grahamthedev/101-digital-accessibility-tips-and-tricks-4728
  3. https://a11y-101.com/design/button-vs-link

Enjoying the content so far? Please comment at dev.to