Business Wish
CtrlK
ComponentsTemplates
CtrlK

Components

Accordion
Alert
Avatar
Badge
Banner
Bottom Navigation
Breadcrumb
Button
Call to Action
Card
Data Table
Date Picker
Divider
Dropdown
Featues
File Upload
Footer
Header
Hero
Loader
Pagination
Popover
Progress
Sidebar
Skeleton
Social Share
Tabs
Testimonial
Tooltip

Pages

Error Pages
Blog List

Pagination

Pagination is a great way to break up large sets of data into smaller, more manageable chunks.

Base Styles

const baseButtonStyles = `
  relative overflow-hidden
  disabled:opacity-50 disabled:cursor-not-allowed focus:outline-none
`;

const rippleEffect = (event: React.MouseEvent<HTMLButtonElement>) => {
  const button = event.currentTarget;
  const ripple = document.createElement("span");
  const rect = button.getBoundingClientRect();
  const size = Math.max(rect.width, rect.height);
  const x = event.clientX - rect.left - size / 2;
  const y = event.clientY - rect.top - size / 2;

  ripple.style.cssText = `
    position: absolute;
    width: ${size}px;
    height: ${size}px;
    top: ${y}px;
    left: ${x}px;
    background-color: rgba(255, 255, 255, 0.7);
    border-radius: 50%;
    transform: scale(0);
    animation: ripple 600ms linear;
    pointer-events: none;
  `;

  button.appendChild(ripple);
  setTimeout(() => ripple.remove(), 600);
};

Basic Pagination

import React, { useState } from 'react';

export const BasicPagination: React.FC = () => {
  const [currentPage, setCurrentPage] = useState(1);
  const totalPages = 3;

return (
    <nav className="flex text-base justify-center">
      <ul className="flex list-none items-center gap-2">
        <li>
          <button
            className={`${baseButtonStyles} px-4 py-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              setCurrentPage((p) => Math.max(1, p - 1));
            }}
            disabled={currentPage === 1}
          >
            Previous
          </button>
        </li>
        {[1, 2, 3].map((page) => (
          <li key={page}>
            <button
              className={`${baseButtonStyles} w-10 h-10 rounded-lg font-medium
                ${
                  currentPage === page
                    ? "bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700"
                    : "bg-white border border-gray-200 text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
                }`}
              onClick={(e) => {
                rippleEffect(e);
                setCurrentPage(page);
              }}
            >
              {page}
            </button>
          </li>
        ))}
        <li>
          <button
            className={`${baseButtonStyles} px-4 py-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              setCurrentPage((p) => Math.min(totalPages, p + 1));
            }}
            disabled={currentPage === totalPages}
          >
            Next
          </button>
        </li>
      </ul>
    </nav>
  );
};

Pagination with Icons

import React, { useState } from 'react';
import { ChevronLeft, ChevronRight } from 'lucide-react';
  return (
    <nav className="flex text-base justify-center">
      <ul className="flex list-none items-center gap-2">
        <li>
          <button
            className={`${baseButtonStyles} px-4 py-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700 flex items-center gap-2`}
            onClick={(e) => {
              rippleEffect(e);
              setCurrentPage((p) => Math.max(1, p - 1));
            }}
            disabled={currentPage === 1}
          >
            <ChevronLeft className="w-4 h-4" />
            <span>Previous</span>
          </button>
        </li>
        {[1, 2, 3].map((page) => (
          <li key={page}>
            <button
              className={`${baseButtonStyles} w-10 h-10 rounded-lg font-medium
                ${
                  currentPage === page
                    ? "bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700"
                    : "bg-white border border-gray-200 text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
                }`}
              onClick={(e) => {
                rippleEffect(e);
                setCurrentPage(page);
              }}
            >
              {page}
            </button>
          </li>
        ))}
        <li>
          <button
            className={`${baseButtonStyles} px-4 py-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700 flex items-center gap-2`}
            onClick={(e) => {
              rippleEffect(e);
              setCurrentPage((p) => Math.min(totalPages, p + 1));
            }}
            disabled={currentPage === totalPages}
          >
            <span>Next</span>
            <ChevronRight className="w-4 h-4" />
          </button>
        </li>
      </ul>
    </nav>
  );
};

Pagination with Input Field

import React, { useState } from 'react';

export const PaginationwithInputField: React.FC = () => {
  const [currentPage, setCurrentPage] = useState(1);
  const [inputValue, setInputValue] = useState('1');
  const totalPages = 10;

  const handlePageChange = (page: number) => {
    if (page >= 1 && page <= totalPages) {
      setCurrentPage(page);
      setInputValue(page.toString());
    }
  };

  return (
    <nav className="flex text-base justify-center">
      <ul className="flex list-none items-center gap-3">
        <li>
          <button
            className={`${baseButtonStyles} p-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              handlePageChange(1);
            }}
            disabled={currentPage === 1}
          >
            <ChevronsLeft className="w-4 h-4" />
          </button>
        </li>
        <li>
          <button
            className={`${baseButtonStyles} p-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              handlePageChange(currentPage - 1);
            }}
            disabled={currentPage === 1}
          >
            <ChevronLeft className="w-4 h-4" />
          </button>
        </li>
        <li className="flex items-center gap-2">
          <input
            type="text"
            className="w-16 px-3 py-2 rounded-lg border border-gray-200 text-center 
              focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
              dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300"
            value={inputValue}
            onChange={(e) => {
              const value = e.target.value;
              setInputValue(value);
              const page = parseInt(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());
              }
            }}
          />
          <span className="text-gray-600 dark:text-gray-400">
            of {totalPages}
          </span>
        </li>
        <li>
          <button
            className={`${baseButtonStyles} p-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              handlePageChange(currentPage + 1);
            }}
            disabled={currentPage === totalPages}
          >
            <ChevronRight className="w-4 h-4" />
          </button>
        </li>
        <li>
          <button
            className={`${baseButtonStyles} p-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              handlePageChange(totalPages);
            }}
            disabled={currentPage === totalPages}
          >
            <ChevronsRight className="w-4 h-4" />
          </button>
        </li>
      </ul>
    </nav>
  );
};

Pagination with Dots

import React, { useState } from 'react';

export const PaginationwithDots: React.FC = () => {
  const [currentPage, setCurrentPage] = useState(1);
  const totalPages = 10;

  const renderPageNumbers = () => {
    const pageNumbers = [];
    const maxVisiblePages = 5;

    if (totalPages <= maxVisiblePages) {
      for (let i = 1; i <= totalPages; i++) {
        pageNumbers.push(i);
      }
    } else {
      if (currentPage <= 3) {
        for (let i = 1; i <= 4; i++) {
          pageNumbers.push(i);
        }
        pageNumbers.push("...");
        pageNumbers.push(totalPages);
      } else if (currentPage >= totalPages - 2) {
        pageNumbers.push(1);
        pageNumbers.push("...");
        for (let i = totalPages - 3; i <= totalPages; i++) {
          pageNumbers.push(i);
        }
      } else {
        pageNumbers.push(1);
        pageNumbers.push("...");
        for (let i = currentPage - 1; i <= currentPage + 1; i++) {
          pageNumbers.push(i);
        }
        pageNumbers.push("...");
        pageNumbers.push(totalPages);
      }
    }

    return pageNumbers.map((number, index) => (
      <li key={index}>
        <button
          className={`${baseButtonStyles} w-10 h-10 rounded-lg font-medium
            ${
              number === currentPage
                ? "bg-blue-500 text-white hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700"
                : number === "..."
                ? "bg-transparent text-gray-400 hover:bg-transparent cursor-default"
                : "bg-white border border-gray-200 text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-700"
            }`}
          onClick={(e) => {
            if (number !== "...") {
              rippleEffect(e);
              setCurrentPage(Number(number));
            }
          }}
          disabled={number === "..."}
        >
          {number}
        </button>
      </li>
    ));
  };

  return (
    <nav className="flex text-base justify-center">
      <ul className="flex list-none items-center gap-2">
        <li>
          <button
            className={`${baseButtonStyles} p-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              setCurrentPage((p) => Math.max(1, p - 1));
            }}
            disabled={currentPage === 1}
          >
            <ChevronLeft className="w-4 h-4" />
          </button>
        </li>
        {renderPageNumbers()}
        <li>
          <button
            className={`${baseButtonStyles} p-2 rounded-lg bg-white border border-gray-200 
              text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:border-gray-700 
              dark:text-gray-300 dark:hover:bg-gray-700`}
            onClick={(e) => {
              rippleEffect(e);
              setCurrentPage((p) => Math.min(totalPages, p + 1));
            }}
            disabled={currentPage === totalPages}
          >
            <ChevronRight className="w-4 h-4" />
          </button>
        </li>
      </ul>
    </nav>
  );
};
PreviousLoader
NextPopover

On this page

Base StylesBasic PaginationPagination with IconsPagination with Input FieldPagination with Dots
Business Wish

Production-ready Tailwind CSS components. Copy, paste, and build beautiful interfaces.

Resources

  • Components
  • Templates

Connect

© 2025 Business Wish. All rights reserved.

Built by Abhay Singh Rathore