Back
Learn how to build a compact pagination component that handles large numbers of pages using ellipsis.
<nav class="pagination" aria-label="Page navigation"> <button class="pagination-item" aria-label="Go to previous page" id="prev-button"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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-list" id="pagination-list"> <button class="pagination-item">1</button> <button class="pagination-item">2</button> <button class="pagination-item active">3</button> <span class="pagination-ellipsis">…</span> <button class="pagination-item">8</button> <button class="pagination-item">9</button> <button class="pagination-item">10</button> </div> <button class="pagination-item" aria-label="Go to next page" id="next-button"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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 elements of our HTML structure: - We use a semantic
nav
element with appropriate ARIA labels for accessibility
- Previous and next buttons with clear SVG icons
- A container for the page numbers and ellipsis
- The ellipsis is represented by the HTML entity …
- The current page has an "active" class
document.addEventListener('DOMContentLoaded', () => { const prevButton = document.getElementById('prev-button'); const nextButton = document.getElementById('next-button'); const paginationList = document.getElementById('pagination-list'); // Configuration const totalPages = 10; let currentPage = 3; function updatePagination() { // Clear current pagination list paginationList.innerHTML = ''; // Helper function to create a page button function createPageButton(pageNum, isActive = false) { const button = document.createElement('button'); button.className = `pagination-item${isActive ? ' active' : ''}`; button.textContent = pageNum; button.addEventListener('click', () => setCurrentPage(pageNum)); button.disabled = isActive; return button; } // Helper function to create an ellipsis function createEllipsis() { const span = document.createElement('span'); span.className = 'pagination-ellipsis'; span.innerHTML = '…'; return span; } // Always show first page paginationList.appendChild(createPageButton(1, currentPage === 1)); // Logic for showing correct pagination items with ellipsis if (currentPage > 3) { paginationList.appendChild(createEllipsis()); } // Show pages around current page const startPage = Math.max(2, currentPage - 1); const endPage = Math.min(totalPages - 1, currentPage + 1); for (let i = startPage; i <= endPage; i++) { if (i <= totalPages - 1 && i >= 2) { paginationList.appendChild(createPageButton(i, currentPage === i)); } } // Add ellipsis before last page if needed if (currentPage < totalPages - 2) { paginationList.appendChild(createEllipsis()); } // Always show last page if (totalPages > 1) { paginationList.appendChild(createPageButton(totalPages, currentPage === totalPages)); } // Update prev/next button states prevButton.disabled = currentPage === 1; nextButton.disabled = currentPage === totalPages; } // Function to set current page and update UI function setCurrentPage(pageNum) { currentPage = pageNum; updatePagination(); // In a real application, you would fetch data for the new page here console.log(`Navigated to page ${currentPage}`); } // Add event listeners to prev/next buttons prevButton.addEventListener('click', () => { if (currentPage > 1) { setCurrentPage(currentPage - 1); } }); nextButton.addEventListener('click', () => { if (currentPage < totalPages) { setCurrentPage(currentPage + 1); } }); // Initialize pagination updatePagination(); });The key features of our JavaScript implementation: - Dynamically generates pagination based on current page position - Always shows the first and last page - Shows a small window of pages around the current page - Uses ellipsis to indicate skipped pages when there are many pages - Handles navigation with previous and next buttons - Updates the UI whenever the current page changes
.pagination { --text-color: #374151; --background: white; --border-color: #e5e7eb; --hover-bg: #f9fafb; --active-bg: #4f46e5; --active-text: white; --disabled-color: #d1d5db; display: flex; align-items: center; gap: 6px; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); padding: 8px 12px; border-radius: 8px; background: var(--background); } .pagination-list { display: flex; align-items: center; gap: 4px; } .pagination-item { display: flex; align-items: center; justify-content: center; min-width: 36px; height: 36px; padding: 0 8px; border: 1px solid var(--border-color); border-radius: 6px; background: var(--background); color: var(--text-color); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; }Let's style the different states of our pagination items:
.pagination-item:hover:not(:disabled):not(.active) { background: var(--hover-bg); border-color: #d1d5db; } .pagination-item.active { background: var(--active-bg); color: var(--active-text); border-color: var(--active-bg); cursor: default; } .pagination-item:focus-visible { outline: 2px solid var(--active-bg); outline-offset: 2px; } .pagination-item:disabled { opacity: 0.6; cursor: not-allowed; } .pagination-ellipsis { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; font-size: 18px; color: var(--text-color); user-select: none; }Finally, let's make our pagination responsive for smaller screens:
/* Responsive styles */ @media (max-width: 480px) { .pagination { padding: 6px 8px; } .pagination-item { min-width: 32px; height: 32px; font-size: 13px; } .pagination-ellipsis { width: 24px; } }This pagination component with ellipsis offers several benefits: - Scalability: Works well with any number of pages, from a few to hundreds - Space efficiency: The ellipsis keeps the pagination compact even with many pages - Context awareness: Always shows the user where they are in relation to the first and last page - Accessibility: Uses semantic HTML and proper ARIA attributes for screen readers - Visual clarity: Clear visual hierarchy with distinct active state and hover effects - Adaptability: Responsive design works across different screen sizes This pattern is ideal for search results, large product catalogs, document libraries, or any application with paginated content that might span many pages.
<nav class="pagination" aria-label="Page navigation"> <button class="pagination-item" aria-label="Go to previous page" id="prev-button"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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-list" id="pagination-list"> <button class="pagination-item">1</button> <button class="pagination-item">2</button> <button class="pagination-item active">3</button> <span class="pagination-ellipsis">…</span> <button class="pagination-item">8</button> <button class="pagination-item">9</button> <button class="pagination-item">10</button> </div> <button class="pagination-item" aria-label="Go to next page" id="next-button"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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 paginationList = document.getElementById('pagination-list'); // Configuration const totalPages = 10; let currentPage = 3; function updatePagination() { // Clear current pagination list paginationList.innerHTML = ''; // Helper function to create a page button function createPageButton(pageNum, isActive = false) { const button = document.createElement('button'); button.className = `pagination-item${isActive ? ' active' : ''}`; button.textContent = pageNum; button.addEventListener('click', () => setCurrentPage(pageNum)); button.disabled = isActive; return button; } // Helper function to create an ellipsis function createEllipsis() { const span = document.createElement('span'); span.className = 'pagination-ellipsis'; span.innerHTML = '…'; return span; } // Always show first page paginationList.appendChild(createPageButton(1, currentPage === 1)); // Logic for showing correct pagination items with ellipsis if (currentPage > 3) { paginationList.appendChild(createEllipsis()); } // Show pages around current page const startPage = Math.max(2, currentPage - 1); const endPage = Math.min(totalPages - 1, currentPage + 1); for (let i = startPage; i <= endPage; i++) { if (i <= totalPages - 1 && i >= 2) { paginationList.appendChild(createPageButton(i, currentPage === i)); } } // Add ellipsis before last page if needed if (currentPage < totalPages - 2) { paginationList.appendChild(createEllipsis()); } // Always show last page if (totalPages > 1) { paginationList.appendChild(createPageButton(totalPages, currentPage === totalPages)); } // Update prev/next button states prevButton.disabled = currentPage === 1; nextButton.disabled = currentPage === totalPages; } // Function to set current page and update UI function setCurrentPage(pageNum) { currentPage = pageNum; updatePagination(); // In a real application, you would fetch data for the new page here console.log(`Navigated to page ${currentPage}`); } // Add event listeners to prev/next buttons prevButton.addEventListener('click', () => { if (currentPage > 1) { setCurrentPage(currentPage - 1); } }); nextButton.addEventListener('click', () => { if (currentPage < totalPages) { setCurrentPage(currentPage + 1); } }); // Initialize pagination updatePagination(); }); </script> <style> .pagination { --text-color: #374151; --background: white; --border-color: #e5e7eb; --hover-bg: #f9fafb; --active-bg: #4f46e5; --active-text: white; --disabled-color: #d1d5db; display: flex; align-items: center; gap: 6px; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); padding: 8px 12px; border-radius: 8px; background: var(--background); } .pagination-list { display: flex; align-items: center; gap: 4px; } .pagination-item { display: flex; align-items: center; justify-content: center; min-width: 36px; height: 36px; padding: 0 8px; border: 1px solid var(--border-color); border-radius: 6px; background: var(--background); color: var(--text-color); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .pagination-item:hover:not(:disabled):not(.active) { background: var(--hover-bg); border-color: #d1d5db; } .pagination-item.active { background: var(--active-bg); color: var(--active-text); border-color: var(--active-bg); cursor: default; } .pagination-item:focus-visible { outline: 2px solid var(--active-bg); outline-offset: 2px; } .pagination-item:disabled { opacity: 0.6; cursor: not-allowed; } .pagination-ellipsis { display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; font-size: 18px; color: var(--text-color); user-select: none; } /* Responsive styles */ @media (max-width: 480px) { .pagination { padding: 6px 8px; } .pagination-item { min-width: 32px; height: 32px; font-size: 13px; } .pagination-ellipsis { width: 24px; } } </style>Thank you for reading this article.
Comments