Back
A tutorial on how to build a clean pagination component using text labels for better readability.
<nav class="text-pagination" aria-label="Page navigation"> <div class="page-controls"> <button class="page-link prev" id="prev-page" aria-label="Go to previous page">Previous</button> <div class="page-numbers"> <button class="page-link number">1</button> <button class="page-link number">2</button> <button class="page-link number active">3</button> <button class="page-link number">4</button> <button class="page-link number">5</button> </div> <button class="page-link next" id="next-page" aria-label="Go to next page">Next</button> </div> <div class="page-info" id="page-info">Page 3 of 5</div> </nav>The key aspects of our HTML structure: - We use a semantic
nav
element with an appropriate aria-label
- Text-based "Previous" and "Next" buttons instead of icon buttons
- Numbered page buttons in the middle
- An informative text showing the current page and total page count
- Clear class names that describe the purpose of each element
document.addEventListener('DOMContentLoaded', () => { const prevButton = document.getElementById('prev-page'); const nextButton = document.getElementById('next-page'); const pageInfo = document.getElementById('page-info'); const pageButtons = document.querySelectorAll('.page-link.number'); // Set initial page let currentPage = 3; const totalPages = pageButtons.length; // Update UI to reflect current page function updatePageUI() { // Update page buttons pageButtons.forEach((button, index) => { const pageNum = index + 1; if (pageNum === currentPage) { button.classList.add('active'); button.setAttribute('aria-current', 'page'); button.disabled = true; } else { button.classList.remove('active'); button.removeAttribute('aria-current'); button.disabled = false; } }); // Update page info text pageInfo.textContent = `Page ${currentPage} of ${totalPages}`; // Enable/disable prev/next buttons prevButton.disabled = currentPage === 1; nextButton.disabled = currentPage === totalPages; // Add/remove disabled class for styling if (currentPage === 1) { prevButton.classList.add('disabled'); } else { prevButton.classList.remove('disabled'); } if (currentPage === totalPages) { nextButton.classList.add('disabled'); } else { nextButton.classList.remove('disabled'); } } // Set page and update UI function goToPage(pageNum) { currentPage = pageNum; updatePageUI(); // In a real app, you would load the content for the new page here console.log(`Navigated to page ${currentPage}`); } // Add click handlers for prev/next buttons prevButton.addEventListener('click', () => { if (currentPage > 1) { goToPage(currentPage - 1); } }); nextButton.addEventListener('click', () => { if (currentPage < totalPages) { goToPage(currentPage + 1); } }); // Add click handlers for page number buttons pageButtons.forEach((button, index) => { button.addEventListener('click', () => { goToPage(index + 1); }); }); // Initialize the UI updatePageUI(); });Our JavaScript includes these key features: - A centralized function to update the UI state - ARIA attributes for better accessibility - Proper disabling of buttons when they're not usable - Event handlers for all interactive elements - Dynamic page information text
/* Main container */ .text-pagination { --primary: #3b82f6; --text: #374151; --border: #e5e7eb; --bg-hover: #f3f4f6; --disabled: #9ca3af; --active-bg: #3b82f6; --active-text: white; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; } /* Controls container */ .page-controls { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; } /* Page numbers container */ .page-numbers { display: flex; gap: 4px; }Now let's style the pagination buttons:
/* All pagination links */ .page-link { background: transparent; border: 1px solid var(--border); padding: 8px 12px; border-radius: 4px; color: var(--text); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } /* Prev/Next buttons */ .page-link.prev, .page-link.next { padding: 8px 16px; } /* Number buttons */ .page-link.number { min-width: 40px; text-align: center; } /* Hover effects */ .page-link:hover:not(:disabled):not(.active) { background-color: var(--bg-hover); border-color: #d1d5db; } /* Active state */ .page-link.active { background-color: var(--active-bg); color: var(--active-text); border-color: var(--active-bg); } /* Disabled state */ .page-link:disabled, .page-link.disabled { opacity: 0.6; cursor: not-allowed; }Let's style the page info text and add focus states for accessibility:
/* Page info text */ .page-info { text-align: center; font-size: 14px; color: var(--text); margin-top: 8px; } /* Focus styles for accessibility */ .page-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }Finally, let's make our pagination responsive for mobile devices:
/* Responsive adjustments */ @media (max-width: 480px) { .page-controls { flex-direction: column; gap: 16px; } .page-numbers { order: 1; } .page-link.prev { order: 2; align-self: flex-start; } .page-link.next { order: 3; align-self: flex-end; margin-top: -44px; /* Align with prev button */ } }This text-based pagination component offers several advantages: - Improved clarity: Text labels like "Previous" and "Next" are more explicit than arrows - Better accessibility: Text labels provide clear meaning for screen readers - Informative context: The page information text helps users understand their location - User-friendly design: Clean layout with proper spacing and visual states - Mobile responsiveness: Adapts to different screen sizes with an optimized layout - Smooth interactions: Subtle transitions and clear hover/active states This pagination style is particularly well-suited for content-focused websites like blogs, articles, or documentation sites where clarity is more important than compact design.
<nav class="text-pagination" aria-label="Page navigation"> <div class="page-controls"> <button class="page-link prev" id="prev-page" aria-label="Go to previous page">Previous</button> <div class="page-numbers"> <button class="page-link number">1</button> <button class="page-link number">2</button> <button class="page-link number active">3</button> <button class="page-link number">4</button> <button class="page-link number">5</button> </div> <button class="page-link next" id="next-page" aria-label="Go to next page">Next</button> </div> <div class="page-info" id="page-info">Page 3 of 5</div> </nav> <script> document.addEventListener('DOMContentLoaded', () => { const prevButton = document.getElementById('prev-page'); const nextButton = document.getElementById('next-page'); const pageInfo = document.getElementById('page-info'); const pageButtons = document.querySelectorAll('.page-link.number'); // Set initial page let currentPage = 3; const totalPages = pageButtons.length; // Update UI to reflect current page function updatePageUI() { // Update page buttons pageButtons.forEach((button, index) => { const pageNum = index + 1; if (pageNum === currentPage) { button.classList.add('active'); button.setAttribute('aria-current', 'page'); button.disabled = true; } else { button.classList.remove('active'); button.removeAttribute('aria-current'); button.disabled = false; } }); // Update page info text pageInfo.textContent = `Page ${currentPage} of ${totalPages}`; // Enable/disable prev/next buttons prevButton.disabled = currentPage === 1; nextButton.disabled = currentPage === totalPages; // Add/remove disabled class for styling if (currentPage === 1) { prevButton.classList.add('disabled'); } else { prevButton.classList.remove('disabled'); } if (currentPage === totalPages) { nextButton.classList.add('disabled'); } else { nextButton.classList.remove('disabled'); } } // Set page and update UI function goToPage(pageNum) { currentPage = pageNum; updatePageUI(); // In a real app, you would load the content for the new page here console.log(`Navigated to page ${currentPage}`); } // Add click handlers for prev/next buttons prevButton.addEventListener('click', () => { if (currentPage > 1) { goToPage(currentPage - 1); } }); nextButton.addEventListener('click', () => { if (currentPage < totalPages) { goToPage(currentPage + 1); } }); // Add click handlers for page number buttons pageButtons.forEach((button, index) => { button.addEventListener('click', () => { goToPage(index + 1); }); }); // Initialize the UI updatePageUI(); }); </script> <style> /* Main container */ .text-pagination { --primary: #3b82f6; --text: #374151; --border: #e5e7eb; --bg-hover: #f3f4f6; --disabled: #9ca3af; --active-bg: #3b82f6; --active-text: white; font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 0 auto; } /* Controls container */ .page-controls { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; } /* Page numbers container */ .page-numbers { display: flex; gap: 4px; } /* All pagination links */ .page-link { background: transparent; border: 1px solid var(--border); padding: 8px 12px; border-radius: 4px; color: var(--text); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } /* Prev/Next buttons */ .page-link.prev, .page-link.next { padding: 8px 16px; } /* Number buttons */ .page-link.number { min-width: 40px; text-align: center; } /* Hover effects */ .page-link:hover:not(:disabled):not(.active) { background-color: var(--bg-hover); border-color: #d1d5db; } /* Active state */ .page-link.active { background-color: var(--active-bg); color: var(--active-text); border-color: var(--active-bg); } /* Disabled state */ .page-link:disabled, .page-link.disabled { opacity: 0.6; cursor: not-allowed; } /* Page info text */ .page-info { text-align: center; font-size: 14px; color: var(--text); margin-top: 8px; } /* Focus styles for accessibility */ .page-link:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; } /* Responsive adjustments */ @media (max-width: 480px) { .page-controls { flex-direction: column; gap: 16px; } .page-numbers { order: 1; } .page-link.prev { order: 2; align-self: flex-start; } .page-link.next { order: 3; align-self: flex-end; margin-top: -44px; /* Align with prev button */ } } </style>Thank you for reading this article.
Comments