Skip to Content

Pagination

Pagination breaks long lists into pages, shows where you are, and offers controls to move between pages. The package is composed of a row, previous/next chevrons, a page container, and individual page controls. The authoritative package is @camp/pagination.

Loading...

Overview

Resources

Loading...

Loading...

Loading...

Loading...

Install

yarn add @camp/pagination

Upgrading to Next Gen

🧩 One package, named exports: Replace @activecampaign/camp-components-pagination with @camp/pagination. You can optionally use named exports (PaginationRow, PaginationButton, PaginationPage, PaginationPageContainer) but the same subcomponents exist (Row, Button, Page, and PageContainer) if you prefer shorter names.

🎨 Next Gen styling and tokens: Layout and colors follow the Camp Design System; chevrons use token-based icons and styled-components.

🔄 You control the active page: The container still expects active (0-based index) and textOnly when you need the compact “Page a/b” label instead of the full number strip. Ellipsis treatment applies automatically when there are more than seven pages.

Previous implementation

import * as Pagination from '@activecampaign/camp-components-pagination'; <Pagination.PaginationRow> <Pagination.PaginationButton prev onClick={() => setActive((i) => Math.max(0, i - 1))} /> <Pagination.PaginationPageContainer active={active} textOnly={false}> {pages.map((n, i) => ( <Pagination.PaginationPage key={n} active={active === i} onClick={() => setActive(i)}> {n} </Pagination.PaginationPage> ))} </Pagination.PaginationPageContainer> <Pagination.PaginationButton prev={false} onClick={() => setActive((i) => Math.min(pages.length - 1, i + 1))} /> </Pagination.PaginationRow>;

New implementation (optional named exports)

import { PaginationButton, PaginationPage, PaginationPageContainer, PaginationRow, } from '@camp/pagination';

Variations

Composed Row (Playground)

A typical wiring: PaginationRow wraps previous/next PaginationButton and a PaginationPageContainer that receives one PaginationPage per page. The parent component owns the active index and updates it from chevrons and number clicks.

import { useState } from 'react'; import { PaginationButton, PaginationPage, PaginationPageContainer, PaginationRow, } from '@camp/pagination'; const pages = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; export const Paginated = () => { const [active, setActive] = useState(0); return ( <PaginationRow> <PaginationButton prev onClick={() => setActive((i) => Math.max(0, i - 1))} /> <PaginationPageContainer active={active} textOnly={false}> {pages.map((page, i) => ( <PaginationPage key={page} active={active === i} onClick={() => setActive(i)}> {page} </PaginationPage> ))} </PaginationPageContainer> <PaginationButton prev={false} onClick={() => setActive((i) => Math.min(pages.length - 1, i + 1))} /> </PaginationRow> ); };

Many Pages (Ellipsis)

With more than seven PaginationPage children, the container collapses the list with ... placeholders. Active page, neighbors, and ends stay visible. Ellipsis controls are not interactive and render as disabled buttons (see Accessibility).

const manyPages = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const active = 4; <PaginationRow> <PaginationButton prev /> <PaginationPageContainer active={active} textOnly={false}> {manyPages.map((page, i) => ( <PaginationPage key={page} active={i === active} onClick={() => {}}> {page} </PaginationPage> ))} </PaginationPageContainer> <PaginationButton prev={false} /> </PaginationRow>;

Text Only

Use textOnly on PaginationPageContainer when space is limited (for example, small viewports). The full strip is hidden and a Page a/b label is shown; keep the same children and active index so the label stays in sync when users move with the chevrons.

import { useState } from 'react'; import { PaginationButton, PaginationPage, PaginationPageContainer, PaginationRow, } from '@camp/pagination'; const pages = [1, 2, 3, 4, 5]; const TextOnlyExample = () => { const [active, setActive] = useState(2); return ( <PaginationRow> <PaginationButton prev onClick={() => setActive((a) => Math.max(0, a - 1))} /> <PaginationPageContainer active={active} textOnly> {pages.map((page, i) => ( <PaginationPage key={page} active={i === active} onClick={() => setActive(i)}> {page} </PaginationPage> ))} </PaginationPageContainer> <PaginationButton prev={false} onClick={() => setActive((a) => Math.min(pages.length - 1, a + 1))} /> </PaginationRow> ); };

Disabled Previous (Boundary)

Disable the previous chevron on the first page (and the next chevron on the last page) so the UI matches boundaries. The icon buttons use title for screen-reader support (Go to previous page / Go to next page).

<PaginationRow> <PaginationButton prev disabled /> <PaginationPageContainer active={0} textOnly={false}> {[1, 2, 3, 4, 5].map((page, i) => ( <PaginationPage key={page} active={i === 0} onClick={() => {}}> {page} </PaginationPage> ))} </PaginationPageContainer> <PaginationButton prev={false} /> </PaginationRow>

Usage

  • State: Store the current page as a 0-based index. Pass it to PaginationPageContainer as active and to each PaginationPage as active={active === index}.
  • Chevrons: PaginationButton with prev (default true) renders the back chevron; set prev={false} for next. Use disabled on the first and last page as needed.
  • Text strip: textOnly replaces the number strip with a Page a/b label. You must still pass PaginationPage children so the container can compute the total; navigation is usually handled only via chevrons in this mode.
  • Composition: PaginationPageContainer with role="navigation" and textOnly={false} wraps the interactive strip; the row is layout-only and does not manage focus.
import { useState } from 'react'; import { PaginationButton, PaginationPage, PaginationPageContainer, PaginationRow, } from '@camp/pagination'; const PAGE_NUMBERS = [1, 2, 3, 4, 5, 6, 7]; export const ListPagination = () => { const [active, setActive] = useState(0); const last = PAGE_NUMBERS.length - 1; return ( <PaginationRow> <PaginationButton prev disabled={active === 0} onClick={() => setActive((i) => Math.max(0, i - 1))} /> <PaginationPageContainer active={active} textOnly={false}> {PAGE_NUMBERS.map((n, i) => ( <PaginationPage key={n} active={active === i} onClick={() => setActive(i)}> {n} </PaginationPage> ))} </PaginationPageContainer> <PaginationButton prev={false} disabled={active === last} onClick={() => setActive((i) => Math.min(last, i + 1))} /> </PaginationRow> ); };

Best Practices

  • Do wire chevrons and PaginationPage onClick to the same active state so the list and the controls never drift.
  • Do pass two or more PaginationPage children; the container is built for multi-page lists.
  • Do set textOnly when the full strip does not fit.
  • Don’t use pagination when everything fits on one page; it adds noise and focus stops.
  • Don’t make ellipsis placeholders interactive—they are not clickable by design and render disabled.

Content Guidelines

✅ DO

  • Use pagination for long tables or long card lists with a clear page size.

  • Disable chevrons on the first and last page so boundaries are obvious.

🚫 DON’T

  • Add pagination when a single scroll, load more, or a virtual list is simpler.

  • Expect the ... controls to jump pages; they are not interactive.

Accessibility

Landmarks and State

  • Region: When textOnly is false, PaginationPageContainer renders role="navigation" and aria-label="pagination". In textOnly mode it uses a Label with a visible Page a/b string and does not use that navigation role on the same structure.
  • Current page: The active PaginationPage sets aria-current="page". Ellipsis rows use disabled buttons, do not receive aria-current, and use the visible ... text.
  • Chevrons: PaginationButton uses an Icon with a title of Go to previous page or Go to next page.

Keyboard Support

  • Tab moves focus through chevrons and number buttons in order.
  • Enter and Space on a focused page number run the same action as a click.

Similar Components

Data Table

Data Table

Intelligent data table component for displaying and managing structured data in AI-powered interfaces

Table

Table

Organizes data in rows and columns, making it easier to compare and analyze.

Data visualization

Data visualization

Comprehensive suite of charting components for displaying data insights

Last updated on