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 componentLink to section

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

Disable impossible navigationLink to section

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

Use ellipsis for large page countsLink to section

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

Sync URL only at the product layerLink to section

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

Pair with loading and empty statesLink to section

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

ComponentsLink to section

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 optionsLink to section

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. Avoid clever wording for core navigation.

Showing 1–10 of 48

If your table needs stronger context, show the range near the pagination controls.

No results found

When filters remove all results, show an empty state and a reset action instead of useless pagination.

Common questionsLink to section

Next step

Ready to go further?

Move from documentation to a real production setup with authentication, billing, and backend foundations already wired.

Was this helpful?