Pagination
Component
Effortless Tailwind pagination and Tailwind CSS pagination components redesigned for ultimate clarity and seamless interactions.
Navigating large datasets shouldn't be a chore. A well-designed Tailwind pagination block provides clear context so users always know exactly where they are. These Tailwind CSS pagination components offer variations ranging from minimal dot indicators to robust numbered lists with custom input fields.
Pagination is most commonly paired directly with a comprehensive Data Table, utilizing optimized Buttons for intuitive next/previous interactions.
Basic Pagination
import React, { useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
const BasicPagination = () => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 5;
return (
<nav className="flex justify-center w-full">
<ul className="flex items-center gap-1.5 bg-white dark:bg-neutral-950 p-1.5 rounded-2xl border border-neutral-200/60 dark:border-white/10 shadow-sm w-fit">
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40 disabled:pointer-events-none"
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
{[...Array(totalPages)].map((_, i) => {
const page = i + 1;
const isActive = currentPage === page;
return (
<li key={page}>
<button
className={`flex items-center justify-center w-9 h-9 rounded-xl text-[14px] font-medium transition-all ${
isActive
? "bg-neutral-900 text-white dark:bg-white dark:text-neutral-900 shadow-md transform scale-105"
: "text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-900 hover:text-neutral-900 dark:hover:text-white"
}`}
onClick={() => setCurrentPage(page)}
>
{page}
</button>
</li>
);
})}
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40 disabled:pointer-events-none"
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
>
<ChevronRight className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
</ul>
</nav>
);
};
export default BasicPagination;
Pagination with Icons
import React, { useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
const PaginationwithIcons = () => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 5;
return (
<nav className="flex justify-center w-full">
<div className="flex items-center gap-2">
<button
className="flex items-center gap-2 px-4 py-2 text-[14px] font-medium text-neutral-600 dark:text-neutral-400 bg-white dark:bg-neutral-950 border border-neutral-200 dark:border-white/10 rounded-xl hover:bg-neutral-50 dark:hover:bg-neutral-900 hover:text-neutral-900 dark:hover:text-white transition-all disabled:opacity-40 disabled:pointer-events-none shadow-sm"
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" strokeWidth={2.5} />
<span>Previous</span>
</button>
<div className="flex items-center gap-1.5 px-1">
{[...Array(totalPages)].map((_, i) => {
const page = i + 1;
const isActive = currentPage === page;
return (
<button
key={page}
className={`flex items-center justify-center w-9 h-9 rounded-xl text-[14px] font-medium transition-all ${
isActive
? "bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-white"
: "text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-50 dark:hover:bg-neutral-900"
}`}
onClick={() => setCurrentPage(page)}
>
{page}
</button>
);
})}
</div>
<button
className="flex items-center gap-2 px-4 py-2 text-[14px] font-medium text-neutral-600 dark:text-neutral-400 bg-white dark:bg-neutral-950 border border-neutral-200 dark:border-white/10 rounded-xl hover:bg-neutral-50 dark:hover:bg-neutral-900 hover:text-neutral-900 dark:hover:text-white transition-all disabled:opacity-40 disabled:pointer-events-none shadow-sm"
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
>
<span>Next</span>
<ChevronRight className="w-4 h-4" strokeWidth={2.5} />
</button>
</div>
</nav>
);
};
export default PaginationwithIcons;
Pagination with Input
import React, { useState } from "react";
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
const PaginationwithInputField = () => {
const [currentPage, setCurrentPage] = useState(1);
const [inputValue, setInputValue] = useState("1");
const totalPages = 10;
const handlePageChange = (page) => {
if (page >= 1 && page <= totalPages) {
setCurrentPage(page);
setInputValue(page.toString());
}
};
return (
<nav className="flex justify-center w-full">
<ul className="flex items-center gap-2 bg-white dark:bg-neutral-950 p-1.5 rounded-2xl border border-neutral-200/60 dark:border-white/10 shadow-sm w-fit">
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40"
onClick={() => handlePageChange(1)}
disabled={currentPage === 1}
>
<ChevronsLeft className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
<li className="flex items-center gap-3 px-2">
<div className="flex items-center gap-1.5">
<span className="text-[13px] font-medium text-neutral-500 uppercase tracking-wider">Page</span>
<input
type="text"
className="w-10 h-8 text-[14px] font-semibold text-center rounded-lg bg-neutral-100 dark:bg-neutral-900 border-none text-neutral-900 dark:text-white focus:ring-2 focus:ring-neutral-200 dark:focus:ring-neutral-800 transition-all"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
const page = parseInt(e.target.value, 10);
if (!isNaN(page) && page >= 1 && page <= totalPages) {
handlePageChange(page);
}
}}
onBlur={() => {
const page = parseInt(inputValue, 10);
if (isNaN(page) || page < 1 || page > totalPages) setInputValue(currentPage.toString());
}}
/>
</div>
<span className="text-[13px] font-medium text-neutral-500">
of <span className="text-neutral-900 dark:text-white font-semibold ml-1">{totalPages}</span>
</span>
</li>
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
>
<ChevronRight className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40"
onClick={() => handlePageChange(totalPages)}
disabled={currentPage === totalPages}
>
<ChevronsRight className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
</ul>
</nav>
);
};
export default PaginationwithInputField;
Pagination with Dots (Ellipsis)
import React, { useState } from "react";
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
const PaginationwithDots = () => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 10;
const renderPageNumbers = () => {
const pages = [];
if (currentPage <= 3) {
pages.push(1, 2, 3, 4, "ellipsis", totalPages);
} else if (currentPage >= totalPages - 2) {
pages.push(1, "ellipsis", totalPages - 3, totalPages - 2, totalPages - 1, totalPages);
} else {
pages.push(1, "ellipsis", currentPage - 1, currentPage, currentPage + 1, "ellipsis", totalPages);
}
return pages.map((item, index) => {
if (item === "ellipsis") {
return (
<li key={`ellipsis-${index}`} className="flex items-center justify-center w-9 h-9 text-neutral-400">
<MoreHorizontal className="w-4 h-4" />
</li>
);
}
const isActive = item === currentPage;
return (
<li key={item}>
<button
className={`flex items-center justify-center w-9 h-9 rounded-xl text-[14px] font-medium transition-all ${
isActive
? "bg-neutral-900 text-white dark:bg-white dark:text-neutral-900 shadow-md transform scale-105"
: "text-neutral-600 dark:text-neutral-400 hover:bg-neutral-100 dark:hover:bg-neutral-900 hover:text-neutral-900 dark:hover:text-white"
}`}
onClick={() => setCurrentPage(item)}
>
{item}
</button>
</li>
);
});
};
return (
<nav className="flex justify-center w-full">
<ul className="flex items-center gap-1.5">
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40 disabled:pointer-events-none"
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
{renderPageNumbers()}
<li>
<button
className="flex items-center justify-center w-9 h-9 rounded-xl text-neutral-500 hover:text-neutral-900 dark:hover:text-white hover:bg-neutral-100 dark:hover:bg-neutral-900 transition-all disabled:opacity-40 disabled:pointer-events-none"
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
>
<ChevronRight className="w-4 h-4" strokeWidth={2.5} />
</button>
</li>
</ul>
</nav>
);
};
export default PaginationwithDots;
Minimal Line Pagination
import React, { useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
const MinimalPagination = () => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 5;
return (
<nav className="flex justify-center w-full">
<div className="flex items-center gap-6">
<button
className="flex items-center gap-2 text-[13px] font-semibold text-neutral-500 hover:text-neutral-900 dark:hover:text-white transition-colors uppercase tracking-widest disabled:opacity-40"
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" /> Prev
</button>
<div className="flex items-center gap-1">
{[...Array(totalPages)].map((_, i) => {
const page = i + 1;
const isActive = currentPage === page;
return (
<button
key={page}
className={`w-2 h-2 rounded-full transition-all duration-300 ${
isActive
? "w-6 bg-neutral-900 dark:bg-white"
: "bg-neutral-300 dark:bg-neutral-700 hover:bg-neutral-400 dark:hover:bg-neutral-500"
}`}
onClick={() => setCurrentPage(page)}
aria-label={`Go to page ${page}`}
/>
);
})}
</div>
<button
className="flex items-center gap-2 text-[13px] font-semibold text-neutral-500 hover:text-neutral-900 dark:hover:text-white transition-colors uppercase tracking-widest disabled:opacity-40"
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
>
Next <ChevronRight className="w-4 h-4" />
</button>
</div>
</nav>
);
};
export default MinimalPagination;
Rounded Ghost Pagination
import React, { useState } from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
const RoundedGhostPagination = () => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = 5;
return (
<nav className="flex justify-center w-full">
<div className="inline-flex items-center p-1 bg-neutral-100/50 dark:bg-neutral-900/50 backdrop-blur-md rounded-full border border-neutral-200/50 dark:border-white/5">
<button
className="flex items-center justify-center w-10 h-10 rounded-full text-neutral-500 hover:bg-white dark:hover:bg-neutral-800 hover:text-neutral-900 dark:hover:text-white hover:shadow-sm transition-all disabled:opacity-40"
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
disabled={currentPage === 1}
>
<ChevronLeft className="w-4 h-4" strokeWidth={2.5} />
</button>
<div className="flex items-center px-2 gap-1 text-[14px]">
<span className="font-semibold text-neutral-900 dark:text-white w-5 text-center">{currentPage}</span>
<span className="text-neutral-400">/</span>
<span className="text-neutral-500 w-5 text-center">{totalPages}</span>
</div>
<button
className="flex items-center justify-center w-10 h-10 rounded-full text-neutral-500 hover:bg-white dark:hover:bg-neutral-800 hover:text-neutral-900 dark:hover:text-white hover:shadow-sm transition-all disabled:opacity-40"
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
disabled={currentPage === totalPages}
>
<ChevronRight className="w-4 h-4" strokeWidth={2.5} />
</button>
</div>
</nav>
);
};
export default RoundedGhostPagination;