Back
Creating a Pagination with Ellipsis for Many Pages
Learn how to build a compact pagination component that handles large numbers of pages using ellipsis.
HTML
Let's create a pagination component that can handle a large number of pages using ellipsis. Here's the HTML structure we'll start with:
<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" classJavaScript
Now let's implement the JavaScript to dynamically generate the pagination based on the current page and total pages:
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 changesCSS
Now let's style our pagination component to make it visually appealing:
.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.Whole code
<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