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!