Pagination is a common feature across many websites, from news archives and product listings to blogs and search results. Despite its simplicity on the surface, pagination is one of those UI patterns that can be surprisingly nuanced when it comes to accessibility. Most developers implement it using visual styling alone, assuming it “just works.” Unfortunately, that’s rarely the case for users relying on screen readers or keyboard navigation. In this primer, we’ll look at what it means to build pagination that is truly accessible and demonstrate how to do so with clean, semantic HTML and just enough CSS to make it practical.
The Role of Pagination in UX and Accessibility
The purpose of pagination is to help users navigate through a large dataset without overwhelming them with too much content at once. For most sighted users, numbered links and arrow icons are intuitive. But for users navigating with a keyboard or assistive technologies like screen readers, pagination needs to be structured in a way that clearly communicates what each link does, which page is currently active, and whether certain navigation options are available.
It’s not enough to just visually style a series of links. The markup must be meaningful and informative to all users. This means using proper HTML elements and supporting attributes that convey relationships and states programmatically. Getting this right ensures that everyone – regardless of how they navigate – can move through your site with confidence and clarity.
Semantic Markup: Why Links Matter More Than Buttons
One of the most common mistakes developers make is using <button> elements for pagination. While buttons may work in the sense that they’re interactive and styleable, they aren’t semantically correct. Pagination typically involves navigation between different resources or views, which makes <a> (anchor) elements the appropriate choice. Links (that is, an <a> element with a valid href attribute) naturally indicate a change in location and are understood by screen readers and browsers as part of a navigational flow.
When you mark up pagination with links, assistive technologies can build a mental model for the user. For example, a screen reader can say, “Link: page 2 of 5,” or, “Link: next page,” giving clear cues about where each link goes and what it means. When a button is used instead, this context is often lost or has to be artificially recreated with additional ARIA roles, making the experience brittle or inconsistent.
A Working Example of Accessible Pagination
Below is a complete example of accessible pagination. It uses a <nav> element to indicate the navigational nature of the content, properly labeled links to help screen reader users understand what each control does, and ARIA attributes to clarify state. The full thing can be seen on Codepen.
Here’s the HTML:
<nav class="pagination" aria-label="Pagination Navigation">
<a href="/" class="page-link prev" aria-label="Previous page" aria-disabled="true" tabindex="-1">«</a>
<a href="?page=1" class="page-link" aria-current="page">1</a>
<a href="?page=2" class="page-link">2</a>
<a href="?page=3" class="page-link">3</a>
<a href="?page=4" class="page-link">4</a>
<a href="?page=2" class="page-link next" aria-label="Next page">»</a>
</nav>
This structure provides a predictable and understandable model for all users. The aria-current=”page” attribute on the first page signals that it is the currently active view. The previous arrow is marked with aria-disabled=”true” and tabindex=”-1″ to prevent focus and indicate its disabled state when on the first page. These small additions make a big difference for accessibility without adding complexity.
And here’s the accompanying CSS:
.pagination {
display: flex;
justify-content: center;
gap: 0.5rem;
margin-top: 2rem;
font-family: sans-serif;
}
.page-link {
padding: 0.5rem 1rem;
text-decoration: none;
border: 1px solid #333;
border-radius: 4px;
color: #333;
background: white;
}
.page-link[aria-current="page"] {
background: #333;
color: white;
font-weight: bold;
pointer-events: none;
}
.page-link[aria-disabled="true"] {
color: #aaa;
border-color: #aaa;
pointer-events: none;
cursor: default;
}
This CSS is intentionally minimal. It gives you a clean and readable design while visually distinguishing the active page from others. The disabled state of the “previous” arrow is styled to appear dimmed and inactive, reflecting the underlying logic in the HTML.
How This Works with Assistive Technologies
A screen reader user tabbing into this navigation component will encounter a series of clearly labeled links. The “Previous” link is skipped entirely because it has a tabindex of -1, and the screen reader will also detect that it’s disabled via aria-disabled. As the user moves through the numbered links, the current page will be announced as such, thanks to the aria-current=”page” attribute.
This is exactly what we want. The pagination component doesn’t require a script to explain itself. It doesn’t need a separate aria-live region to narrate changes or focus management hacks. By using the right elements and attributes, we’re able to deliver an accessible experience that is both user-friendly and standards-compliant.
When JavaScript Comes Into Play
In many modern applications—especially SPAs built with React, Vue, or similar frameworks—pagination is handled dynamically. If you’re not performing full-page reloads and instead fetching data from an API, you’ll likely want to update the active page state in JavaScript.
Even in those scenarios, you can still use the same markup pattern. Just be sure to update the aria-current attribute and the href values accordingly. It’s also important to handle focus appropriately. If clicking a pagination link causes new content to load, it’s good practice to shift focus to the new content region so users aren’t left guessing what changed.
Final Thoughts
With the right structure and a little attention to detail, pagination can be both beautiful and inclusive. This example gives you a foundation that’s ready to use in production or adapt for your own design system.
If you’re building a component library or designing a reusable pattern, it’s worth taking a few minutes to test your pagination with a screen reader and a keyboard. You might be surprised at how small changes, like swapping a button for a link or adding an aria-current attribute, can drastically improve the experience.
The next time you’re working on a paginated view, consider using this approach. It’s simple, it’s semantic, and most importantly, it works for everyone.


