Pagination
A navigation component for dividing content across multiple pages with numbered links and next/previous controls.
Usage
Basic Pagination
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@prisma-docs/eclipse";
export function BasicPagination() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}Live Example:
With Ellipsis for Many Pages
For large page counts, use ellipsis to collapse intermediate pages:
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
} from "@prisma-docs/eclipse";
export function PaginationWithEllipsis() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">8</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
9
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">10</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">20</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}Live Example:
With Page Input
For quick navigation to a specific page, use the PaginationInput component:
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationInput,
} from "@prisma-docs/eclipse";
import { useState } from "react";
export function PaginationWithInput() {
const [currentPage, setCurrentPage] = useState(5);
const totalPages = 20;
const handlePageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const page = parseInt(e.target.value, 10);
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) setCurrentPage(currentPage - 1);
}}
/>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationInput
value={currentPage}
onChange={handlePageChange}
totalPages={totalPages}
/>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">{totalPages}</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}Live Example:
Using Next.js Link
You can use the pagination with Next.js Link component:
import Link from "next/link";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@prisma-docs/eclipse";
export function NextLinkPagination({ currentPage }: { currentPage: number }) {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href={`/docs?page=${currentPage - 1}`} />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/docs?page=1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/docs?page=2" isActive={currentPage === 2}>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/docs?page=3">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href={`/docs?page=${currentPage + 1}`} />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}Dynamic Pagination Logic
Here's a complete example with dynamic page generation:
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
} from "@prisma-docs/eclipse";
interface DynamicPaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
}
export function DynamicPagination({
currentPage,
totalPages,
onPageChange,
}: DynamicPaginationProps) {
const pages = [];
const showEllipsisStart = currentPage > 3;
const showEllipsisEnd = currentPage < totalPages - 2;
// Always show first page
pages.push(1);
// Show ellipsis if current page is far from start
if (showEllipsisStart) {
pages.push(-1); // -1 represents ellipsis
}
// Show pages around current page
for (
let i = Math.max(2, currentPage - 1);
i <= Math.min(totalPages - 1, currentPage + 1);
i++
) {
pages.push(i);
}
// Show ellipsis if current page is far from end
if (showEllipsisEnd) {
pages.push(-2); // -2 represents ellipsis
}
// Always show last page (if more than 1 page)
if (totalPages > 1) {
pages.push(totalPages);
}
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) onPageChange(currentPage - 1);
}}
aria-disabled={currentPage === 1}
/>
</PaginationItem>
{pages.map((page, index) => (
<PaginationItem key={index}>
{page < 0 ? (
<PaginationEllipsis />
) : (
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
onPageChange(page);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
)}
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) onPageChange(currentPage + 1);
}}
aria-disabled={currentPage === totalPages}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}Live Example:
API Reference
Pagination
The root container for pagination navigation.
Props:
- Extends all standard HTML
<nav>attributes className- Additional CSS classes (optional)
PaginationContent
The list container for pagination items.
Props:
- Extends all standard HTML
<ul>attributes className- Additional CSS classes (optional)
PaginationItem
Individual pagination item wrapper.
Props:
- Extends all standard HTML
<li>attributes className- Additional CSS classes (optional)
PaginationLink
Link element for page numbers.
Props:
isActive- Whether this page is currently active (boolean, default: false)size- Button size:"sm","md","lg","xl","icon"(default:"lg")className- Additional CSS classes (optional)- Extends all standard HTML
<a>attributes
PaginationPrevious
Previous page navigation button with icon and label.
Props:
className- Additional CSS classes (optional)- Extends all
PaginationLinkprops - Automatically includes ChevronLeft icon and "Previous" label
PaginationNext
Next page navigation button with icon and label.
Props:
className- Additional CSS classes (optional)- Extends all
PaginationLinkprops - Automatically includes ChevronRight icon and "Next" label
PaginationEllipsis
Ellipsis indicator for collapsed pages.
Props:
className- Additional CSS classes (optional)- Extends all standard HTML
<span>attributes
PaginationInput
Input field for direct page navigation.
Props:
value- Current page number (number, optional)onChange- Callback when page input changes ((e: React.ChangeEvent<HTMLInputElement>) => void, optional)totalPages- Total number of pages (number, optional)className- Additional CSS classes (optional)- Extends all standard HTML
<div>attributes
Features
- ✅ Semantic HTML structure with proper ARIA attributes
- ✅ Accessible navigation with screen reader support
- ✅ Previous/Next buttons with icons
- ✅ Active page indication
- ✅ Ellipsis support for large page counts
- ✅ Direct page input for quick navigation
- ✅ Customizable size variants
- ✅ Support for custom link components (Next.js Link, etc.)
- ✅ Responsive design
- ✅ Eclipse design system styling
- ✅ Keyboard navigable
Best Practices
- Always indicate the current active page with
isActive - Use ellipsis when there are more than 7-9 pages
- Show pages immediately around the current page (± 1 or 2)
- Always show first and last pages
- Disable Previous/Next buttons at boundaries
- Use descriptive
aria-labelattributes for accessibility - Ensure all page links are functional and update the view
- Place pagination at the bottom of content
- Consider showing total page count or items for context
- Use consistent pagination across your application
- For very large datasets, consider adding a "jump to page" input
Accessibility
The Pagination component follows accessibility best practices:
- Uses semantic
<nav>element witharia-label="pagination" - Current page is marked with
aria-current="page" - Previous/Next buttons have descriptive
aria-labelattributes - Ellipsis is hidden from screen readers with
aria-hidden="true" - Keyboard navigable with Tab and Enter/Space keys
- Proper focus management and visual indicators
- Screen reader friendly structure
Common Patterns
Minimal Pagination (Few Pages)
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="/page/1" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/2" isActive>2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/3">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="/page/3" />
</PaginationItem>
</PaginationContent>
</Pagination>Extended Pagination (Many Pages)
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="/page/14" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/14">14</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/15" isActive>15</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/16">16</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/50">50</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="/page/16" />
</PaginationItem>
</PaginationContent>
</Pagination>First Page
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" aria-disabled="true" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/1" isActive>1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/2">2</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/3">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/20">20</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="/page/2" />
</PaginationItem>
</PaginationContent>
</Pagination>Last Page
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="/page/19" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/1">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/18">18</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/19">19</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="/page/20" isActive>20</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" aria-disabled="true" />
</PaginationItem>
</PaginationContent>
</Pagination>Integration Examples
With Data Fetching
"use client";
import { useState, useEffect } from "react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
} from "@prisma-docs/eclipse";
export function PaginatedList() {
const [currentPage, setCurrentPage] = useState(1);
const [data, setData] = useState([]);
const [totalPages, setTotalPages] = useState(0);
useEffect(() => {
async function fetchData() {
const response = await fetch(`/api/items?page=${currentPage}`);
const result = await response.json();
setData(result.items);
setTotalPages(result.totalPages);
}
fetchData();
}, [currentPage]);
return (
<div>
<div className="grid gap-4">
{data.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
<Pagination className="mt-8">
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) setCurrentPage(currentPage - 1);
}}
/>
</PaginationItem>
{/* Render page numbers dynamically */}
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
<PaginationItem key={page}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(page);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
}With URL Search Params
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@prisma-docs/eclipse";
export function URLPagination({ totalPages }: { totalPages: number }) {
const router = useRouter();
const searchParams = useSearchParams();
const currentPage = Number(searchParams.get("page")) || 1;
const updatePage = (page: number) => {
const params = new URLSearchParams(searchParams);
params.set("page", page.toString());
router.push(`?${params.toString()}`);
};
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) updatePage(currentPage - 1);
}}
/>
</PaginationItem>
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
<PaginationItem key={page}>
<PaginationLink
href={`?page=${page}`}
onClick={(e) => {
e.preventDefault();
updatePage(page);
}}
isActive={currentPage === page}
>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) updatePage(currentPage + 1);
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}With Page Input for Large Datasets
"use client";
import { useState } from "react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
PaginationInput,
} from "@prisma-docs/eclipse";
export function LargeDatasetPagination({ totalPages }: { totalPages: number }) {
const [currentPage, setCurrentPage] = useState(1);
const handlePageInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const page = parseInt(e.target.value, 10);
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
}
};
return (
<div>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage > 1) setCurrentPage(currentPage - 1);
}}
/>
</PaginationItem>
<PaginationItem>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(1);
}}
isActive={currentPage === 1}
>
1
</PaginationLink>
</PaginationItem>
{currentPage > 3 && (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
)}
<PaginationItem>
<PaginationInput
value={currentPage}
onChange={handlePageInput}
totalPages={totalPages}
/>
</PaginationItem>
{currentPage < totalPages - 2 && (
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
)}
<PaginationItem>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
setCurrentPage(totalPages);
}}
isActive={currentPage === totalPages}
>
{totalPages}
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (currentPage < totalPages) setCurrentPage(currentPage + 1);
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
);
}Styling
The Pagination component uses button variants for consistent styling:
- Active pages use the
"default"variant - Inactive pages use the
"default-weaker"variant - Size defaults to
"lg"but can be customized - All styling is integrated with the Eclipse design system
Related Components
- Breadcrumb - For showing hierarchical navigation
- Button - The underlying component used for links
- Tabs - For content organization without pagination