Pagination

A navigation component for dividing content across multiple pages with numbered links and next/previous controls.

Usage

Basic Pagination

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationPrevious,
  PaginationNext,
} from "@prisma-docs/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:

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-docs/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:

With Page Input

For quick navigation to a specific page, use the PaginationInput component:

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationPrevious,
  PaginationNext,
  PaginationInput,
} from "@prisma-docs/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:

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-docs/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-docs/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:

API Reference

Pagination

The root container for pagination navigation.

Props:

  • Extends all standard HTML <nav> attributes
  • className - Additional CSS classes (optional)

PaginationContent

The list container for pagination items.

Props:

  • Extends all standard HTML <ul> attributes
  • className - Additional CSS classes (optional)

PaginationItem

Individual pagination item wrapper.

Props:

  • Extends all standard HTML <li> attributes
  • className - 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)
  • Extends all standard HTML <a> attributes

PaginationPrevious

Previous page navigation button with icon and label.

Props:

  • className - Additional CSS classes (optional)
  • Extends all PaginationLink props
  • Automatically includes ChevronLeft icon and "Previous" label

PaginationNext

Next page navigation button with icon and label.

Props:

  • className - Additional CSS classes (optional)
  • Extends all PaginationLink props
  • Automatically includes ChevronRight icon and "Next" label

PaginationEllipsis

Ellipsis indicator for collapsed pages.

Props:

  • className - Additional CSS classes (optional)
  • Extends all standard HTML <span> attributes

PaginationInput

Input 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)
  • Extends all standard HTML <div> attributes

Features

  • ✅ Semantic HTML structure with proper ARIA attributes
  • ✅ Accessible navigation with screen reader support
  • ✅ Previous/Next buttons with icons
  • ✅ Active page indication
  • ✅ Ellipsis support for large page counts
  • ✅ Direct page input for quick navigation
  • ✅ Customizable size variants
  • ✅ Support for custom link components (Next.js Link, etc.)
  • ✅ Responsive design
  • ✅ Eclipse design system styling
  • ✅ Keyboard navigable

Best Practices

  • Always indicate the current active page with isActive
  • Use ellipsis when there are more than 7-9 pages
  • Show pages immediately around the current page (± 1 or 2)
  • Always show first and last pages
  • Disable Previous/Next buttons at boundaries
  • Use descriptive aria-label attributes for accessibility
  • Ensure all page links are functional and update the view
  • Place pagination at the bottom of content
  • Consider showing total page count or items for context
  • Use consistent pagination across your application
  • For very large datasets, consider adding a "jump to page" input

Accessibility

The Pagination component follows accessibility best practices:

  • Uses semantic <nav> element with aria-label="pagination"
  • Current page is marked with aria-current="page"
  • Previous/Next buttons have descriptive aria-label attributes
  • Ellipsis is hidden from screen readers with aria-hidden="true"
  • Keyboard navigable with Tab and Enter/Space keys
  • Proper focus management and visual indicators
  • Screen reader friendly structure

Common Patterns

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>

Integration Examples

With Data Fetching

"use client";

import { useState, useEffect } from "react";
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationPrevious,
  PaginationNext,
  PaginationEllipsis,
} from "@prisma-docs/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>
          {/* Render page numbers dynamically */}
          {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-docs/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-docs/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>
  );
}

Styling

The Pagination component uses button variants for consistent styling:

  • Active pages use the "default" variant
  • Inactive pages use the "default-weaker" variant
  • Size defaults to "lg" but can be customized
  • All styling is integrated with the Eclipse design system
  • Breadcrumb - For showing hierarchical navigation
  • Button - The underlying component used for links
  • Tabs - For content organization without pagination

On this page