Pagination

Composable pagination primitives for navigating paged content without routing or data logic.

The Pagination component provides composable UI primitives for navigating paged content.

It is intentionally dumb:

  • no router integration
  • no fetching logic
  • no URL sync
  • no cursor logic

Pagination is fully controlled by the page or data layer. The UI only renders navigation.


Import

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationPrevious,
  PaginationNext,
  PaginationEllipsis,
  buildPaginationRange,
} from "@/components/ui/pagination";

Usage

Pagination is composed of small building blocks:

  • Pagination → wrapper (nav)
  • PaginationContent → list container (ul)
  • PaginationItem → item wrapper (li)
  • PaginationLink → page button (active state)
  • PaginationPrevious / PaginationNext → navigation
  • PaginationEllipsis → compact overflow indicator

Control page state from your page or component.


Basic pagination

Page 1 / 12

function PaginationBasicExample() {
  const [page, setPage] = React.useState(1);
  const totalPages = 12;

  const tokens = buildPaginationRange({ page, totalPages });

  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious
            disabled={page === 1}
            onClick={() => setPage((p) => Math.max(1, p - 1))}
          />
        </PaginationItem>

        {tokens.map((t) =>
          t.type === "ellipsis" ? (
            <PaginationItem key={t.key}>
              <PaginationEllipsis />
            </PaginationItem>
          ) : (
            <PaginationItem key={t.value}>
              <PaginationLink
                isActive={t.isActive}
                onClick={() => setPage(t.value)}
              >
                {t.value}
              </PaginationLink>
            </PaginationItem>
          )
        )}

        <PaginationItem>
          <PaginationNext
            disabled={page === totalPages}
            onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

Table + Pagination (mock data)

This example demonstrates a realistic SaaS pattern: a Table with a local Pagination control.

No backend logic is included — replace the mock slice with your real data layer later.

Mock data — swap with Prisma later.
NameStatusUpdatedActions
Project 1Active2026-01-21
Project 2Active2026-01-21
Project 3Paused2026-01-21
Project 4Active2026-01-21
Project 5Archived2026-01-21

Showing 15 of 23

type Project = {
  id: string;
  name: string;
  status: "active" | "paused" | "archived";
  updatedAt: string;
};

// Example idea (pseudo):
// - keep `page` in state
// - compute a slice from your dataset
// - pass `page` + `totalPages` into Pagination
//
// const pageSize = 10;
// const totalPages = Math.ceil(projects.length / pageSize);
// const pageProjects = projects.slice((page - 1) * pageSize, page * pageSize);

API

Components

ComponentDescription
PaginationWrapper nav element (aria-label="Pagination")
PaginationContentList container (ul)
PaginationItemItem wrapper (li)
PaginationLinkPage button with active state
PaginationPreviousPrevious button (responsive label)
PaginationNextNext button (responsive label)
PaginationEllipsisVisual overflow indicator
PropTypeDefaultDescription
isActivebooleanfalseMarks the current page (aria-current="page")
sizestring"icon-sm"Maps to Button sizes
classNamestringAdditional Tailwind classes

Accessibility

  • Uses semantic nav with aria-label="Pagination".
  • Active page sets aria-current="page".
  • Buttons remain fully keyboard accessible.
  • Ellipsis is non-interactive and screen-reader labeled.

Design guidelines

  • Keep pagination UI-only, controlled by the page.
  • Prefer small, consistent page sizes for tables.
  • Use ellipsis for large page counts.
  • Sync with URL and data layers in guides or patterns, not inside the component.

When not to use Pagination

Do not use Pagination for:

  • infinite scroll experiences
  • cursor-based APIs without a page concept
  • global navigation logic

Those belong in patterns or product-level implementations.