Pagination Component
Understanding Pagination
Pagination is essential for managing large datasets, search results, article archives, or any content that's too extensive to display on a single page.
When to Use Pagination:
- Search results (Google-style pagination)
- E-commerce product listings
- Blog/article archives
- Forum thread listings
- Admin dashboards with data tables
- Photo galleries or image collections
Key Benefits:
- Improved Performance: Loads content incrementally
- Better UX: Reduces scrolling fatigue
- SEO Friendly: Separate URLs for each page
- Accessibility: Keyboard navigation support
- Mobile Responsive: Adapts to screen size
Live Examples
1. Basic Pagination
2. Pagination with Alignment Options
3. Pagination with Icons Only
4. Responsive Pagination
5. Pagination with Dropdown
JavaScript Integration
Dynamic Pagination Example:
This example shows how to integrate pagination with JavaScript for dynamic content loading.
// HTML Structure
<ul id="dynamicPagination" class="uk-pagination" uk-margin>
<!-- Dynamic content will be inserted here -->
</ul>
<div id="contentContainer">
<!-- Page content goes here -->
</div>
// JavaScript Implementation
class PaginationManager {
constructor(totalPages = 20, currentPage = 1) {
this.totalPages = totalPages;
this.currentPage = currentPage;
this.paginationEl = document.getElementById('dynamicPagination');
this.contentEl = document.getElementById('contentContainer');
this.init();
}
init() {
this.renderPagination();
this.loadPage(this.currentPage);
this.attachEvents();
}
renderPagination() {
let html = '';
// Previous button
if (this.currentPage > 1) {
html += `<li><a href="#" data-page="${this.currentPage - 1}">
<span uk-pagination-previous></span> Previous</a></li>`;
} else {
html += '<li class="uk-disabled"><span><span uk-pagination-previous></span> Previous</span></li>';
}
// Page numbers
const maxVisible = 5;
let start = Math.max(1, this.currentPage - Math.floor(maxVisible / 2));
let end = Math.min(this.totalPages, start + maxVisible - 1);
if (end - start + 1 < maxVisible) {
start = Math.max(1, end - maxVisible + 1);
}
// First page with ellipsis
if (start > 1) {
html += '<li><a href="#" data-page="1">1</a></li>';
if (start > 2) html += '<li><span>...</span></li>';
}
// Page range
for (let i = start; i <= end; i++) {
if (i === this.currentPage) {
html += `<li class="uk-active"><span>${i}</span></li>`;
} else {
html += `<li><a href="#" data-page="${i}">${i}</a></li>`;
}
}
// Last page with ellipsis
if (end < this.totalPages) {
if (end < this.totalPages - 1) html += '<li><span>...</span></li>';
html += `<li><a href="#" data-page="${this.totalPages}">${this.totalPages}</a></li>`;
}
// Next button
if (this.currentPage < this.totalPages) {
html += `<li><a href="#" data-page="${this.currentPage + 1}">
Next <span uk-pagination-next></span></a></li>`;
} else {
html += '<li class="uk-disabled"><span>Next <span uk-pagination-next></span></span></li>';
}
this.paginationEl.innerHTML = html;
}
loadPage(page) {
this.currentPage = page;
this.renderPagination();
// Simulate API call
this.contentEl.innerHTML = `<div class="uk-alert-primary" uk-alert>
<p>Loading content for page ${page}... (Simulated API call)</p>
</div>`;
// Update URL without page reload
window.history.pushState({page}, `Page ${page}`, `?page=${page}`);
}
attachEvents() {
this.paginationEl.addEventListener('click', (e) => {
e.preventDefault();
const link = e.target.closest('a[data-page]');
if (link) {
const page = parseInt(link.dataset.page);
this.loadPage(page);
}
});
// Handle browser back/forward
window.addEventListener('popstate', (e) => {
if (e.state && e.state.page) {
this.loadPage(e.state.page);
}
});
}
}
// Initialize pagination
const pagination = new PaginationManager(20, 1);Common JavaScript Methods:
// 1. Programmatically change page
function goToPage(pageNumber) {
const pagination = new PaginationManager(20, pageNumber);
pagination.loadPage(pageNumber);
}
// 2. Update pagination after search/filter
function updatePagination(totalItems, itemsPerPage) {
const totalPages = Math.ceil(totalItems / itemsPerPage);
const pagination = new PaginationManager(totalPages, 1);
}
// 3. AJAX Pagination Example
async function loadPageViaAJAX(page) {
try {
const response = await fetch(`/api/data?page=${page}&limit=10`);
const data = await response.json();
// Update content
document.getElementById('content').innerHTML = data.html;
// Update pagination
const pagination = new PaginationManager(data.totalPages, page);
// Update browser URL
history.replaceState({}, '', `?page=${page}`);
} catch (error) {
console.error('Error loading page:', error);
UIkit.notification('Failed to load page content', 'danger');
}
}
// 4. Event Listeners for Custom Controls
document.addEventListener('DOMContentLoaded', function() {
// Custom "Go to page" input
const goToPageInput = document.getElementById('goToPage');
const goToPageButton = document.getElementById('goToPageBtn');
goToPageButton.addEventListener('click', function() {
const page = parseInt(goToPageInput.value);
if (page && page > 0) {
goToPagePage(page);
}
});
// Keyboard shortcuts
document.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
// Previous page
pagination.loadPage(Math.max(1, pagination.currentPage - 1));
} else if (e.key === 'ArrowRight') {
// Next page
pagination.loadPage(Math.min(pagination.totalPages,
pagination.currentPage + 1));
}
});
});Complete Production Example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UIKit Pagination - Complete Example</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.19.2/dist/css/uikit.min.css" />
<style>
:root {
--pagination-active-bg: #1e87f0;
--pagination-active-color: #fff;
--pagination-hover-bg: #f8f8f8;
}
.custom-pagination .uk-active > span {
background-color: var(--pagination-active-bg);
color: var(--pagination-active-color);
border-radius: 3px;
}
.custom-pagination a:hover {
background-color: var(--pagination-hover-bg);
border-radius: 3px;
}
.results-info {
font-size: 0.9rem;
color: #666;
}
@media (max-width: 640px) {
.pagination-info {
flex-direction: column;
align-items: flex-start !important;
}
}
</style>
</head>
<body>
<div class="uk-container uk-container-expand uk-margin-top">
<h1>Product Catalog</h1>
<!-- Results information -->
<div class="uk-flex uk-flex-between uk-flex-middle pagination-info uk-margin-bottom">
<div class="results-info">
Showing 1-10 of 200 products
</div>
<div class="uk-form-controls">
<label for="itemsPerPage" class="uk-form-label">Items per page:</label>
<select id="itemsPerPage" class="uk-select uk-form-width-small">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
<!-- Main pagination -->
<ul class="uk-pagination uk-flex-center custom-pagination" uk-margin>
<li><a href="#"><span uk-pagination-previous></span></a></li>
<li><a href="#">1</a></li>
<li class="uk-active"><span>2</span></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><span>...</span></li>
<li><a href="#">20</a></li>
<li><a href="#"><span uk-pagination-next></span></a></li>
</ul>
<!-- Content area -->
<div class="uk-grid uk-grid-small uk-child-width-1-2@s uk-child-width-1-3@m uk-child-width-1-4@l" uk-grid>
<!-- Product cards would go here -->
</div>
<!-- Bottom pagination -->
<div class="uk-margin-large-top">
<ul class="uk-pagination uk-flex-center" uk-margin>
<li><a href="#"><span uk-pagination-previous></span> Previous</a></li>
<li><a href="#">1</a></li>
<li class="uk-active"><span>2</span></li>
<li><a href="#">3</a></li>
<li><a href="#">4</a></li>
<li><a href="#">5</a></li>
<li><span>...</span></li>
<li><a href="#">20</a></li>
<li><a href="#">Next <span uk-pagination-next></span></a></li>
</ul>
</div>
<!-- Jump to page -->
<div class="uk-flex uk-flex-center uk-margin-top">
<div class="uk-inline">
<form class="uk-form">
<div class="uk-flex">
<input class="uk-input uk-form-width-small"
type="number"
min="1"
max="20"
placeholder="Page"
id="jumpToPage">
<button class="uk-button uk-button-default"
type="button"
id="jumpBtn">
Go
</button>
</div>
</form>
</div>
</div>
</div>
<!-- UIKit JS -->
<script src="https://cdn.jsdelivr.net/npm/uikit@3.19.2/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.19.2/dist/js/uikit-icons.min.js"></script>
<script>
// Enhanced Pagination with all features
document.addEventListener('DOMContentLoaded', function() {
const pagination = {
currentPage: 2,
totalPages: 20,
itemsPerPage: 10,
init() {
this.setupEventListeners();
this.updateResultsInfo();
},
setupEventListeners() {
// Page click handlers
document.querySelectorAll('.uk-pagination a').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
this.handlePageClick(e.target);
});
});
// Items per page change
document.getElementById('itemsPerPage').addEventListener('change', (e) => {
this.itemsPerPage = parseInt(e.target.value);
this.recalculatePages();
this.updateResultsInfo();
});
// Jump to page
document.getElementById('jumpBtn').addEventListener('click', () => {
this.jumpToPage();
});
// Enter key in jump input
document.getElementById('jumpToPage').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.jumpToPage();
}
});
},
handlePageClick(element) {
const link = element.closest('a');
if (!link) return;
const text = link.textContent.trim();
if (text.includes('Previous') || link.querySelector('[uk-pagination-previous]')) {
this.goToPage(Math.max(1, this.currentPage - 1));
} else if (text.includes('Next') || link.querySelector('[uk-pagination-next]')) {
this.goToPage(Math.min(this.totalPages, this.currentPage + 1));
} else if (!isNaN(parseInt(text))) {
this.goToPage(parseInt(text));
}
},
goToPage(page) {
if (page === this.currentPage) return;
// Update active state
document.querySelectorAll('.uk-pagination .uk-active').forEach(el => {
el.classList.remove('uk-active');
});
// Find and activate the clicked page
document.querySelectorAll('.uk-pagination a').forEach(link => {
if (parseInt(link.textContent) === page) {
link.parentNode.classList.add('uk-active');
link.outerHTML = `<span>${page}</span>`;
}
});
this.currentPage = page;
this.updateResultsInfo();
this.loadPageContent(page);
// Scroll to top smoothly
window.scrollTo({ top: 0, behavior: 'smooth' });
// Show notification
UIkit.notification({
message: `Loaded page ${page}`,
status: 'success',
pos: 'top-center',
timeout: 1000
});
},
jumpToPage() {
const input = document.getElementById('jumpToPage');
const page = parseInt(input.value);
if (page && page >= 1 && page <= this.totalPages) {
this.goToPage(page);
input.value = '';
} else {
UIkit.notification({
message: `Please enter a page between 1 and ${this.totalPages}`,
status: 'warning',
pos: 'top-center'
});
}
},
recalculatePages() {
// In a real app, you would recalculate total pages based on items
// For this example, we'll simulate it
this.totalPages = Math.ceil(200 / this.itemsPerPage);
this.updatePaginationUI();
},
updatePaginationUI() {
// Update pagination controls based on new total pages
// This is simplified - in reality you'd regenerate the pagination
console.log(`Updated to ${this.totalPages} total pages`);
},
updateResultsInfo() {
const start = (this.currentPage - 1) * this.itemsPerPage + 1;
const end = Math.min(this.currentPage * this.itemsPerPage, 200);
document.querySelector('.results-info').textContent =
`Showing ${start}-${end} of 200 products`;
},
loadPageContent(page) {
// Simulate loading content
console.log(`Loading content for page ${page}...`);
// In a real application, you would:
// 1. Show loading spinner
// 2. Make AJAX request to fetch page data
// 3. Update the content area
// 4. Hide loading spinner
}
};
pagination.init();
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' && !e.target.matches('input, textarea')) {
pagination.goToPage(Math.max(1, pagination.currentPage - 1));
} else if (e.key === 'ArrowRight' && !e.target.matches('input, textarea')) {
pagination.goToPage(Math.min(pagination.totalPages, pagination.currentPage + 1));
}
});
});
</script>
</body>
</html>Pagination Best Practices
Accessibility
- Use semantic HTML (
<nav>element) - Provide ARIA labels for screen readers
- Ensure keyboard navigation works
- Add proper focus states
- Use descriptive link text
UX Considerations
- Show current page clearly
- Include Previous/Next buttons
- Add &qout;First&qout; and &qout;Last&qout; for many pages
- Consider mobile touch targets
- Provide page number input for large datasets
Performance
- Implement AJAX pagination for SPAs
- Use URL parameters for bookmarking
- Cache frequently accessed pages
- Lazy load page content
- Consider infinite scroll for certain use cases
When to Use Different Pagination Patterns:
- Numbered Pagination: Search results, e-commerce, blogs
- &qout;Load More&qout; Button: Social feeds, news sites
- Infinite Scroll: Content discovery, image galleries
- Table Pagination: Admin dashboards, data tables
Related UIKit Components
Tab
For content within the same page context
Accordion
For vertically collapsing content sections
Slider
For horizontal content navigation
Breadcrumb
For hierarchical navigation
Summary
UIKit's Pagination component provides a robust, accessible foundation for page navigation. When implementing pagination:
- Choose the right pattern for your content type
- Ensure accessibility for all users
- Optimize for both desktop and mobile
- Consider implementing AJAX for smoother UX
- Test with real users to validate your implementation
For complex implementations, consider combining pagination with UIKit's Filter and Sort components for complete data navigation solutions.