Back
A tutorial on how to build an accessible, responsive pagination component with previous/next buttons and page indicators.
nav element for accessibility:
<nav class="pagination-container" aria-label="Pagination Navigation">
<button class="pagination-button" id="prev-button" aria-label="Previous page" title="Previous page">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<div class="pagination-numbers" id="pagination-numbers">
<button class="pagination-number">1</button>
<button class="pagination-number">2</button>
<button class="pagination-number active">3</button>
<button class="pagination-number">4</button>
<button class="pagination-number">5</button>
<button class="pagination-number">6</button>
</div>
<button class="pagination-button" id="next-button" aria-label="Next page" title="Next page">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</nav>
Key aspects of our HTML structure:
- We use a semantic nav element with aria-label for screen readers
- Previous and next buttons with SVG icons for a clean look
- Page number buttons in a container, with an "active" class on the current page
- Proper accessibility attributes like aria-label and title
document.addEventListener('DOMContentLoaded', () => {
const prevButton = document.getElementById('prev-button');
const nextButton = document.getElementById('next-button');
const paginationNumbers = document.querySelectorAll('.pagination-number');
// Set the current page (for demo purposes)
let currentPage = 3;
const totalPages = paginationNumbers.length;
// Add click event listeners to pagination numbers
paginationNumbers.forEach((button, index) => {
const pageIndex = index + 1;
button.addEventListener('click', () => {
setCurrentPage(pageIndex);
});
});
// Add click event listeners to prev/next buttons
prevButton.addEventListener('click', () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
});
nextButton.addEventListener('click', () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
});
// Function to set the current page
function setCurrentPage(pageNum) {
currentPage = pageNum;
// Remove active class from all pagination numbers
paginationNumbers.forEach(button => {
button.classList.remove('active');
// Ensure the button is enabled
button.disabled = false;
});
// Add active class to the current page
paginationNumbers[pageNum - 1].classList.add('active');
paginationNumbers[pageNum - 1].disabled = true;
// Disable/enable prev/next buttons based on current page
prevButton.disabled = currentPage === 1;
nextButton.disabled = currentPage === totalPages;
// You would typically fetch new data or change the view here
// For example: fetchPageData(currentPage);
}
// Initialize the pagination
setCurrentPage(currentPage);
});
Our JavaScript provides these key features:
- Event listeners for all pagination buttons
- A main function to handle page changes
- Proper state management for the current page
- Disabling/enabling buttons as appropriate (prev/next/current)
- It initializes with page 3 active as a demo
/* Container styles */
.pagination-container {
--primary-color: #4f46e5;
--disabled-color: #e5e7eb;
--text-color: #374151;
--hover-bg: #f3f4f6;
--active-bg: #4f46e5;
--active-text: white;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Button styles */
.pagination-button,
.pagination-number {
background: transparent;
border: none;
height: 40px;
width: 40px;
cursor: pointer;
border-radius: 6px;
color: var(--text-color);
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
We use CSS variables to keep our styling consistent and easy to modify:
/* Prev/Next buttons */
.pagination-button {
color: var(--text-color);
}
.pagination-button:hover:not(:disabled) {
background: var(--hover-bg);
}
.pagination-button:disabled {
color: var(--disabled-color);
cursor: not-allowed;
}
/* Page number buttons */
.pagination-numbers {
display: flex;
gap: 4px;
}
.pagination-number {
font-weight: 500;
font-size: 14px;
}
.pagination-number:hover:not(.active) {
background: var(--hover-bg);
}
.pagination-number.active {
background: var(--active-bg);
color: var(--active-text);
font-weight: 600;
cursor: default;
}
Finally, let's add some responsive design to ensure our pagination looks good on all devices:
/* Responsive styles */
@media screen and (max-width: 480px) {
.pagination-numbers {
gap: 2px;
}
.pagination-button,
.pagination-number {
height: 36px;
width: 36px;
}
}
This modern pagination component has several advantages:
- Accessibility: Proper semantic HTML and ARIA attributes for screen readers
- Intuitive UX: Clear visual feedback with hover and active states
- Responsive: Adapts to different screen sizes
- Clean design: Minimalist appearance with subtle animations
- Flexible: Easy to integrate with any content that requires pagination
- Developer-friendly: Well-organized code with CSS variables for easy customization
This pagination component would be ideal for content-heavy websites, e-commerce product listings, search results pages, or any interface where users need to navigate through multiple pages of content.<nav class="pagination-container" aria-label="Pagination Navigation">
<button class="pagination-button" id="prev-button" aria-label="Previous page" title="Previous page">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="15 18 9 12 15 6"></polyline>
</svg>
</button>
<div class="pagination-numbers" id="pagination-numbers">
<button class="pagination-number">1</button>
<button class="pagination-number">2</button>
<button class="pagination-number active">3</button>
<button class="pagination-number">4</button>
<button class="pagination-number">5</button>
<button class="pagination-number">6</button>
</div>
<button class="pagination-button" id="next-button" aria-label="Next page" title="Next page">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</nav>
<script>
document.addEventListener('DOMContentLoaded', () => {
const prevButton = document.getElementById('prev-button');
const nextButton = document.getElementById('next-button');
const paginationNumbers = document.querySelectorAll('.pagination-number');
// Set the current page (for demo purposes)
let currentPage = 3;
const totalPages = paginationNumbers.length;
// Add click event listeners to pagination numbers
paginationNumbers.forEach((button, index) => {
const pageIndex = index + 1;
button.addEventListener('click', () => {
setCurrentPage(pageIndex);
});
});
// Add click event listeners to prev/next buttons
prevButton.addEventListener('click', () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
});
nextButton.addEventListener('click', () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
});
// Function to set the current page
function setCurrentPage(pageNum) {
currentPage = pageNum;
// Remove active class from all pagination numbers
paginationNumbers.forEach(button => {
button.classList.remove('active');
// Ensure the button is enabled
button.disabled = false;
});
// Add active class to the current page
paginationNumbers[pageNum - 1].classList.add('active');
paginationNumbers[pageNum - 1].disabled = true;
// Disable/enable prev/next buttons based on current page
prevButton.disabled = currentPage === 1;
nextButton.disabled = currentPage === totalPages;
// You would typically fetch new data or change the view here
// For example: fetchPageData(currentPage);
}
// Initialize the pagination
setCurrentPage(currentPage);
});
</script>
<style>
/* Container styles */
.pagination-container {
--primary-color: #4f46e5;
--disabled-color: #e5e7eb;
--text-color: #374151;
--hover-bg: #f3f4f6;
--active-bg: #4f46e5;
--active-text: white;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
/* Button styles */
.pagination-button,
.pagination-number {
background: transparent;
border: none;
height: 40px;
width: 40px;
cursor: pointer;
border-radius: 6px;
color: var(--text-color);
transition: all 0.2s ease;
display: flex;
align-items: center;
justify-content: center;
}
/* Prev/Next buttons */
.pagination-button {
color: var(--text-color);
}
.pagination-button:hover:not(:disabled) {
background: var(--hover-bg);
}
.pagination-button:disabled {
color: var(--disabled-color);
cursor: not-allowed;
}
/* Page number buttons */
.pagination-numbers {
display: flex;
gap: 4px;
}
.pagination-number {
font-weight: 500;
font-size: 14px;
}
.pagination-number:hover:not(.active) {
background: var(--hover-bg);
}
.pagination-number.active {
background: var(--active-bg);
color: var(--active-text);
font-weight: 600;
cursor: default;
}
/* Responsive styles */
@media screen and (max-width: 480px) {
.pagination-numbers {
gap: 2px;
}
.pagination-button,
.pagination-number {
height: 36px;
width: 36px;
}
}
</style>
Thank you for reading this article.
Comments
No comments yet. Be the first to comment!