Pagination
ComponentsA navigation component for chunking large datasets across multiple indexed pages.
Pagination Bar
Enables users to navigate through segmented records, catalogs, and search queries with clean boundary indicators and fluid active indicator pill animations.
Live Functional Playground Interactive Hooks • useState
Motion Physics & Animation Showcase Framer Motion ✨
Format:
Required Animation Library: framer-motion & lucide-react
Install this package in your project before importing the animated component.
npm i framer-motion lucide-react <nav aria-label="Pagination" class="flex items-center space-x-1">
<button class="px-2.5 py-1.5 rounded-lg border border-border text-xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer" aria-label="Previous page">
‹ Prev
</button>
<button class="w-8 h-8 rounded-lg bg-primary text-primary-foreground font-semibold text-xs flex items-center justify-center shadow-xs">
1
</button>
<button class="w-8 h-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted text-xs flex items-center justify-center transition-colors">
2
</button>
<button class="w-8 h-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted text-xs flex items-center justify-center transition-colors">
3
</button>
<span class="w-8 h-8 text-muted-foreground text-xs flex items-center justify-center">…</span>
<button class="w-8 h-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted text-xs flex items-center justify-center transition-colors">
10
</button>
<button class="px-2.5 py-1.5 rounded-lg border border-border text-xs text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer" aria-label="Next page">
Next ›
</button>
</nav> import React from "react";
export function Pagination({ currentPage = 1, totalPages = 10, onPageChange }: { currentPage?: number; totalPages?: number; onPageChange?: (p: number) => void }) {
return (
<nav aria-label="Pagination" className="flex items-center space-x-1">
<button onClick={() => onPageChange?.(currentPage - 1)} disabled={currentPage <= 1} className="px-2.5 py-1.5 text-xs rounded-lg border">Prev</button>
<span className="text-xs px-2">{currentPage} of {totalPages}</span>
<button onClick={() => onPageChange?.(currentPage + 1)} disabled={currentPage >= totalPages} className="px-2.5 py-1.5 text-xs rounded-lg border">Next</button>
</nav>
);
} "use client";
import React, { useState } from "react";
export interface PaginationProps {
totalPages?: number;
initialPage?: number;
onPageChange?: (page: number) => void;
}
export default function Pagination({
totalPages = 8,
initialPage = 1,
onPageChange,
}: PaginationProps) {
const [currentPage, setCurrentPage] = useState(initialPage);
const goTo = (page: number) => {
if (page < 1 || page > totalPages) return;
setCurrentPage(page);
onPageChange?.(page);
};
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
return (
<nav aria-label="Pagination Navigation" className="flex items-center space-x-1.5 p-2 rounded-xl border border-border bg-card shadow-2xs">
{/* Prev Button */}
<button
type="button"
onClick={() => goTo(currentPage - 1)}
disabled={currentPage === 1}
className="px-3 py-1.5 rounded-lg border border-border text-xs font-medium text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors cursor-pointer"
aria-label="Go to previous page"
>
‹ Prev
</button>
{/* Number Buttons */}
<div className="flex items-center space-x-1">
{pages.map((p) => {
const isActive = currentPage === p;
return (
<button
key={p}
type="button"
onClick={() => goTo(p)}
aria-current={isActive ? "page" : undefined}
className={`w-8 h-8 rounded-lg text-xs font-semibold transition-all cursor-pointer ${
isActive
? "bg-primary text-primary-foreground shadow-xs font-bold"
: "text-muted-foreground hover:text-foreground hover:bg-muted"
}`}
>
{p}
</button>
);
})}
</div>
{/* Next Button */}
<button
type="button"
onClick={() => goTo(currentPage + 1)}
disabled={currentPage === totalPages}
className="px-3 py-1.5 rounded-lg border border-border text-xs font-medium text-foreground hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed transition-colors cursor-pointer"
aria-label="Go to next page"
>
Next ›
</button>
</nav>
);
} "use client";
import React, { useState } from "react";
import { motion } from "framer-motion";
import { ChevronLeft, ChevronRight } from "lucide-react";
export default function AnimatedPagination() {
const [currentPage, setCurrentPage] = useState(2);
const total = 5;
return (
<nav aria-label="Pagination" className="flex items-center gap-1.5 p-2 rounded-2xl border border-border bg-card/70 backdrop-blur-md shadow-xs">
<motion.button
whileTap={{ scale: 0.92 }}
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
className="p-2 rounded-xl text-muted-foreground hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed hover:bg-muted transition-colors cursor-pointer"
>
<ChevronLeft className="h-4 w-4" />
</motion.button>
<div className="flex items-center gap-1 relative">
{Array.from({ length: total }, (_, i) => i + 1).map((page) => {
const isActive = currentPage === page;
return (
<motion.button
key={page}
whileTap={{ scale: 0.92 }}
onClick={() => setCurrentPage(page)}
className={`relative z-10 w-9 h-9 rounded-xl text-xs font-semibold flex items-center justify-center transition-colors cursor-pointer ${
isActive ? "text-primary-foreground font-bold" : "text-muted-foreground hover:text-foreground"
}`}
>
{isActive && (
<motion.div
layoutId="activePagePill"
transition={{ type: "spring", stiffness: 450, damping: 30 }}
className="absolute inset-0 bg-primary rounded-xl -z-10 shadow-sm"
/>
)}
{page}
</motion.button>
);
})}
</div>
<motion.button
whileTap={{ scale: 0.92 }}
onClick={() => setCurrentPage((p) => Math.min(total, p + 1))}
disabled={currentPage === total}
className="p-2 rounded-xl text-muted-foreground hover:text-foreground disabled:opacity-30 disabled:cursor-not-allowed hover:bg-muted transition-colors cursor-pointer"
>
<ChevronRight className="h-4 w-4" />
</motion.button>
</nav>
);
} Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
totalPages | number | 8 | Maximum count of accessible pages |
initialPage | number | 1 | Starting active page index |
onPageChange | (page: number) => void | undefined | Callback invoked with the newly selected page number |
Keyboard & Screen Reader Accessibility
- Landmark Role: Rendered within
<nav aria-label="Pagination">to support assistive screen navigation. - Active Page Semantics: Incorporates
aria-current="page"on the currently selected numeric button. - Boundary Disablement: Previous and Next buttons automatically receive
disabledand visual opacity indicators when at min/max extremes.