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;