UIUpdated April 28, 2026

Pagination

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

UIPagination

Controlled navigation for paged contentLink to section

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

Use it for:

  • data tables
  • admin lists
  • reports
  • invoices
  • users
  • projects
  • search results
  • paged dashboard views

Pagination is intentionally dumb. It does not own routing, fetching, URL sync, cursor logic, page-size logic, or server state.

The page or data layer owns the logic. The UI renders navigation.

Core idea

Pagination should render page navigation, not own the data model.

ImportLink to section

pagination-import.tsx
import { Pagination } from "@pycolors/ui";

Basic usageLink to section

Pagination is composed from small primitives:

  • Pagination → wrapper navigation element
  • PaginationContent → list container
  • PaginationItem → item wrapper
  • PaginationLink → page control
  • PaginationPrevious / PaginationNext → directional controls
  • PaginationEllipsis → compact overflow indicator

Page 1 / 12

pagination-basic-example.tsx
"use client";

import * as React from "react";
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationPrevious,
  PaginationNext,
  PaginationEllipsis,
  buildPaginationRange,
} from "@pycolors/ui";

export 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((current) => Math.max(1, current - 1))}
          />
        </PaginationItem>

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

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

When to use PaginationLink to section

Use Pagination when users need controlled access to a finite set of pages.

Tables

Use pagination when dense tables need stable page navigation.

Paged data

Use when the backend or data layer exposes page-based navigation.

Search results

Use when results are finite and users need predictable navigation.

Table + PaginationLink to section

This example demonstrates a common SaaS pattern: a Table with 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

projects-table-pagination.tsx
type Project = {
  id: string;
  name: string;
  status: "active" | "paused" | "archived";
  updatedAt: string;
};

const pageSize = 10;
const totalPages = Math.ceil(projects.length / pageSize);
const pageProjects = projects.slice(
  (page - 1) * pageSize,
  page * pageSize,
);

// Pass `page`, `totalPages`, and handlers to Pagination.
// Keep data slicing, fetching, and URL sync outside the UI primitive.

Architecture rule

Pagination should receive state and callbacks. The feature layer should own the data source, filters, sorting, and URL sync.

buildPaginationRangeLink to section

Use buildPaginationRange to generate compact page tokens with ellipsis.

build-pagination-range.ts
const tokens = buildPaginationRange({
  page,
  totalPages,
});

The returned tokens can represent:

  • page numbers
  • active page
  • ellipsis markers

This keeps rendering logic clean while keeping Pagination controlled.

Usage patternsLink to section

Keep page state outside the component

Store page, pageSize, and totalPages in the page, feature hook, or data layer.

Disable impossible navigation

Disable Previous on the first page and Next on the last page.

Use ellipsis for large page counts

Avoid rendering dozens of page links. Compact large ranges with ellipsis.

Sync URL only at the product layer

URL syncing belongs to your page or routing layer, not the UI primitive.

Pair with loading and empty states

Pagination should work with Table, loading states, empty states, and filters as one product pattern.

Pagination vs infinite scrollLink to section

SituationUse
Admin tablePagination
Search results with known page countPagination
Invoices, users, subscriptionsPagination
Feed-style browsingInfinite scroll
Cursor-only API with no page conceptProduct-level cursor controls
Small data setNo pagination

Decision rule

Use Pagination when the user benefits from a stable position in a finite result set.

APILink to section

Component

ComponentDescription
PaginationWrapper nav element with pagination semantics
PaginationContentList container
PaginationItemItem wrapper
PaginationLinkPage control with active state
PaginationPreviousPrevious button
PaginationNextNext button
PaginationEllipsisVisual overflow indicator
PropTypeDefaultDescription
isActivebooleanfalseMarks the current page
sizestring"icon-sm"Maps to Button sizes
classNamestringAdditional Tailwind classes

buildPaginationRange options

OptionTypeDescription
pagenumberCurrent page
totalPagesnumberTotal number of pages

AccessibilityLink to section

Pagination should be understandable and keyboard accessible.

Guidelines:

  • use semantic nav with pagination labeling
  • mark the active page with aria-current="page"
  • keep buttons keyboard accessible
  • disable Previous and Next when unavailable
  • make ellipsis non-interactive
  • avoid tiny click targets in dense layouts
  • provide URL sync at the page layer when deep linking matters

Accessibility rule

Pagination controls should be navigable, understandable, and predictable without relying on visual position alone.

Prefer / avoidLink to section

Prefer

  • controlled page state
  • disabled unavailable controls
  • ellipsis for large page counts
  • URL sync in the route layer when needed
  • clear loading and empty states around paged data

Avoid

  • fetching data inside Pagination
  • rendering every page for large ranges
  • clickable ellipsis
  • pagination for tiny data sets
  • using pagination for feed-style discovery

Product copy guidelinesLink to section

Pagination labels should be short and predictable.

Previous / Next

Use familiar labels for core navigation. Avoid clever wording or ambiguous actions.

Previous · Next

Common questionsLink to section