Components

Pagination

View in Figma

Presentational pagination primitives for URL-owned page links, previous/next controls, and ellipses.

Install

npx shadcn@latest add @alix/pagination

Preview

Usage

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious href="/insights?page=4" />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="/insights?page=5" aria-label="Go to page 5" isActive>
        5
      </PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext href="/insights?page=6" />
    </PaginationItem>
  </PaginationContent>
</Pagination>

Examples

01

Compact page range

Use only the local window of pages when the full result set is small enough to avoid ellipses.

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious href="/insights?page=1" />
    </PaginationItem>
    {[1, 2, 3].map((page) => (
      <PaginationItem key={page}>
        <PaginationLink
          href={`/insights?page=${page}`}
          aria-label={`Go to page ${page}`}
          isActive={page === 2}
        >
          {page}
        </PaginationLink>
      </PaginationItem>
    ))}
    <PaginationItem>
      <PaginationNext href="/insights?page=3" />
    </PaginationItem>
  </PaginationContent>
</Pagination>
02

Custom control labels

Previous and next labels can match the content model while keeping the same accessible names.

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious href="/insights?page=1" text="Newer" />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="/insights?page=2" aria-label="Go to page 2" isActive>
        2
      </PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext href="/insights?page=3" text="Older" />
    </PaginationItem>
  </PaginationContent>
</Pagination>
03

Framework link render slot

Pass a rendered link element when a router needs to own navigation while Pagination owns state styling.

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious render={<a href="/services?page=1" />} />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink
        aria-label="Go to page 2"
        render={<a href="/services?page=2" />}
        isActive
      >
        2
      </PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext render={<a href="/services?page=3" />} />
    </PaginationItem>
  </PaginationContent>
</Pagination>
04

Table footer navigation

Panel frames the dataset, DataTable owns rows, and Pagination sits in the footer for URL-owned pages.

Insight queue

Editorial review

Insight queue
InsightOwnerStatus
Turnaround outlookA. WardReady
Working capital signalsM. KhanReview
<Panel
  title="Insight queue"
  description="Editorial review"
  flush
  footer={
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="/insights?page=1" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="/insights?page=2" aria-label="Go to page 2" isActive>
            2
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="/insights?page=3" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  }
>
  <DataTable
    caption="Insight queue"
    dense
    rows={[
      { title: "Turnaround outlook", owner: "A. Ward", status: "Ready", tone: "success" as const },
      { title: "Working capital signals", owner: "M. Khan", status: "Review", tone: "warning" as const },
    ]}
    columns={[
      { key: "title", header: "Insight", cell: (row) => row.title, cellClassName: "font-medium" },
      { key: "owner", header: "Owner", cell: (row) => row.owner },
      { key: "status", header: "Status", cell: (row) => <Badge variant={row.tone}>{row.status}</Badge> },
    ]}
  />
</Panel>

When to use it

Use it for

  • URL-owned result pages for insights, people, posts, or dashboard ledgers.
  • Current page state where aria-current and a visible active treatment are required.
  • Panel footers when a table or card list spans multiple server-rendered pages.

Reach for something else

  • Hierarchy/path context for the current page — use Breadcrumbs instead.
  • Switching views without changing the URL — use Tabs instead.
  • Infinite or cursor-loaded feeds — use Button for a load-more action instead.

Props

PropTypeDefaultDescription
PaginationLink.isActivebooleanfalseMarks the current page with aria-current and the active visual style.
PaginationLink.aria-labelstringRequired accessible name, such as "Go to page 5", because the visible label is only a number.
renderReactElementOptional framework link slot; pagination state is merged onto the rendered element.
PaginationPrevious / PaginationNext.textstring"Previous" / "Next"Visible label beside the chevron on wider screens.

See also