Pagination
Composable pagination primitives for navigating paged content without routing or data logic.
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
import { Pagination } from "@pycolors/ui";Basic usageLink to section
Pagination is composed from small primitives:
Pagination→ wrapper navigation elementPaginationContent→ list containerPaginationItem→ item wrapperPaginationLink→ page controlPaginationPrevious/PaginationNext→ directional controlsPaginationEllipsis→ compact overflow indicator
Page 1 / 12
"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.
| Name | Status | Updated | Actions |
|---|---|---|---|
| Project 1 | Active | 2026-01-21 | |
| Project 2 | Active | 2026-01-21 | |
| Project 3 | Paused | 2026-01-21 | |
| Project 4 | Active | 2026-01-21 | |
| Project 5 | Archived | 2026-01-21 |
Showing 1–5 of 23
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.
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
| Situation | Use |
|---|---|
| Admin table | Pagination |
| Search results with known page count | Pagination |
| Invoices, users, subscriptions | Pagination |
| Feed-style browsing | Infinite scroll |
| Cursor-only API with no page concept | Product-level cursor controls |
| Small data set | No pagination |
Decision rule
Use Pagination when the user benefits from a stable position in a finite result set.
APILink to section
ComponentsLink to section
| Component | Description |
|---|---|
Pagination | Wrapper nav element with pagination semantics |
PaginationContent | List container |
PaginationItem | Item wrapper |
PaginationLink | Page control with active state |
PaginationPrevious | Previous button |
PaginationNext | Next button |
PaginationEllipsis | Visual overflow indicator |
PaginationLink propsLink to section
| Prop | Type | Default | Description |
|---|---|---|---|
isActive | boolean | false | Marks the current page |
size | string | "icon-sm" | Maps to Button sizes |
className | string | — | Additional Tailwind classes |
buildPaginationRange optionsLink to section
| Option | Type | Description |
|---|---|---|
page | number | Current page |
totalPages | number | Total number of pages |
AccessibilityLink to section
Pagination should be understandable and keyboard accessible.
Guidelines:
- use semantic
navwith 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.