Skip to content
LogoLogo

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...

Loading...

Overview

Resources

Loading...

Loading...

Loading...

Loading...

Install

yarn add @camp/pagination

Props

Pagination.PaginationRow

PropTypeDefaultDescription
themedboolean—Whether the row should include light/dark theme modes.

Generated from @camp/pagination@6.7.1 source, ordered by production usage. * required.

Pagination.PaginationPageContainer

PropTypeDefaultDescription
active *number0The active page
children *ReactNode—Two or more <Page /> components
textOnly *booleanfalseText only mode

Generated from @camp/pagination@6.7.1 source, ordered by production usage. * required.

Pagination.PaginationPage

PropTypeDefaultDescription
children *ReactNode—Should be a string containing only an integer
activebooleanfalseIs the page currently active?
ellipsebooleanfalseShould the page be ellipsified? Ellipsifying the page prevents attaching an onClick handler
onClick() => void—Onclick handler for the page

Generated from @camp/pagination@6.7.1 source, ordered by production usage. * required.

Pagination.PaginationButton

PropTypeDefaultDescription
disabledboolean—Is the button in a disabled state
onClick() => void—Onclick handler
prevbooleantrueIs the button prev?

Generated from @camp/pagination@6.7.1 source, ordered by production usage. * required.

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.

🔆 Theming support: PaginationRow now accepts a themed prop (defaults to false). When themed is omitted or false, the component renders in light mode without requiring a ThemeProvider. Pass themed={true} and wrap with a ThemeProvider to enable light/dark mode support.

🔄 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 a translated title for screen-reader support.

<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 renders a Text element (as a span) with a visible Page a/b string and does not use that navigation role.
  • 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 renders an Icon with a translated title (camp-pagination:previous-page / camp-pagination:next-page) for screen-reader support.

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

Copyright © 2026 ActiveCampaign