apps/eclipse/content/design-system/components/pagination.mdx
import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis, PaginationInput, } from "@prisma/eclipse"; import { BasicPaginationExample, PaginationWithEllipsisExample, PaginationWithInputExample, DynamicPaginationExample, } from "@/components/pagination-examples/interactive-examples";
Basic Pagination
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
} from "@prisma/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:
<div className="not-prose"> <BasicPaginationExample /> </div>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/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:
<div className="not-prose"> <PaginationWithEllipsisExample /> </div>With Page Input
For quick navigation to a specific page, use the PaginationInput component:
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationInput,
} from "@prisma/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:
<div className="not-prose"> <PaginationWithInputExample /> </div>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/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/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:
<div className="not-prose"> <DynamicPaginationExample /> </div>The root container for pagination navigation.
Props:
<nav> attributesclassName - Additional CSS classes (optional)The list container for pagination items.
Props:
<ul> attributesclassName - Additional CSS classes (optional)Individual pagination item wrapper.
Props:
<li> attributesclassName - Additional CSS classes (optional)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)<a> attributesPrevious page navigation button with icon and label.
Props:
className - Additional CSS classes (optional)PaginationLink propsNext page navigation button with icon and label.
Props:
className - Additional CSS classes (optional)PaginationLink propsEllipsis indicator for collapsed pages.
Props:
className - Additional CSS classes (optional)<span> attributesInput 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)<div> attributesisActivearia-label attributes for accessibilityThe Pagination component follows accessibility best practices:
<nav> element with aria-label="pagination"aria-current="page"aria-label attributesaria-hidden="true"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>
With Data Fetching
"use client";
import { useState, useEffect } from "react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
} from "@prisma/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>
{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/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/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>
);
}
The Pagination component uses button variants for consistent styling:
"default" variant"default-weaker" variant"lg" but can be customized