1
ComponentsNavigation

Pagination

Accessible page-based navigation with active links, previous and next controls, and skipped ranges.

Purpose

Use Pagination for finite, page-based navigation when people need to move between known result pages.

Paged navigation

Previous, next and page controls update the active page while the demo keeps the page state locally.

Page 1 of 3
const [page, setPage] = useState(1);
const pages = [1, 2, 3];

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious
        className={buttonVariants({ size: "default", variant: "ghost" })}
        onClick={() => setPage((current) => Math.max(1, current - 1))}
        render={<button disabled={page === 1} type="button" />}
      />
    </PaginationItem>
    {pages.map((pageNumber) => (
      <PaginationItem key={pageNumber}>
        <PaginationLink
          aria-label={pageNumber === page ? `Page ${pageNumber}` : `Go to page ${pageNumber}`}
          className={buttonVariants({
            size: "icon",
            variant: pageNumber === page ? "outline" : "ghost",
          })}
          isActive={pageNumber === page}
          onClick={() => setPage(pageNumber)}
          render={<button type="button" />}
        >
          {pageNumber}
        </PaginationLink>
      </PaginationItem>
    ))}
    <PaginationItem>
      <PaginationNext
        className={buttonVariants({ size: "default", variant: "ghost" })}
        onClick={() => setPage((current) => Math.min(pages.length, current + 1))}
        render={<button disabled={page === pages.length} type="button" />}
      />
    </PaginationItem>
  </PaginationContent>
</Pagination>

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/pagination

Import

import { Pagination } from "@/components/ui/pagination";
import { PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Pagination to move through discrete result pages. Keep current page state visible and links accessible.

import { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";

export function Example() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem><PaginationPrevious href="#" /></PaginationItem>
        <PaginationItem><PaginationLink href="#" isActive>1</PaginationLink></PaginationItem>
        <PaginationItem><PaginationNext href="#" /></PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

Examples

Active pages, navigation links, ellipsis and controlled state are compositions of the same slots.

Active page

isActive sets aria-current="page" and switches the link to the outline look.

<Pagination>
  <PaginationContent>
    {[4, 5, 6].map((page) => (
      <PaginationItem key={page}>
        <PaginationLink
          aria-label={page === 5 ? "Page 5" : `Go to page ${page}`}
          href="#"
          isActive={page === 5}
        >
          {page}
        </PaginationLink>
      </PaginationItem>
    ))}
  </PaginationContent>
</Pagination>

Previous and next

Labels collapse to icon-only square buttons on small screens.

<Pagination>
  <PaginationContent>
    <PaginationItem><PaginationPrevious href="#" /></PaginationItem>
    <PaginationItem><PaginationNext href="#" /></PaginationItem>
  </PaginationContent>
</Pagination>

Ellipsis

The ellipsis is aria-hidden and carries a screen-reader-only "More pages" label.

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationLink aria-label="Go to page 1" href="#">1</PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationEllipsis />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink aria-label="Go to page 10" href="#">10</PaginationLink>
    </PaginationItem>
  </PaginationContent>
</Pagination>

Controlled with buttons

The render prop swaps the link for a button; styling comes from buttonVariants, state from useState.

Page 1 of 3
const [page, setPage] = useState(1);
const pages = [1, 2, 3];

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious
        className={buttonVariants({ size: "default", variant: "ghost" })}
        onClick={() => setPage((current) => Math.max(1, current - 1))}
        render={<button disabled={page === 1} type="button" />}
      />
    </PaginationItem>
    {pages.map((pageNumber) => (
      <PaginationItem key={pageNumber}>
        <PaginationLink
          aria-label={pageNumber === page ? `Page ${pageNumber}` : `Go to page ${pageNumber}`}
          className={buttonVariants({
            size: "icon",
            variant: pageNumber === page ? "outline" : "ghost",
          })}
          isActive={pageNumber === page}
          onClick={() => setPage(pageNumber)}
          render={<button type="button" />}
        >
          {pageNumber}
        </PaginationLink>
      </PaginationItem>
    ))}
    <PaginationItem>
      <PaginationNext
        className={buttonVariants({ size: "default", variant: "ghost" })}
        onClick={() => setPage((current) => Math.min(pages.length, current + 1))}
        render={<button disabled={page === pages.length} type="button" />}
      />
    </PaginationItem>
  </PaginationContent>
</Pagination>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Compose Pagination, PaginationContent and PaginationItem as a navigation list.
  • Pagination is compositional and does not own page state: connect links to real routes or control the active page in the consuming app.
  • PaginationLink accepts isActive and every Button size: xs, sm, default, lg, xl, icon-xs, icon-sm, icon, icon-lg and icon-xl.
  • Use PaginationPrevious, PaginationNext and PaginationEllipsis for navigation and skipped ranges.
  • Use the Base UI render prop on links when integrating a router without introducing nested interactive elements.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Pagination has no mount or exit animation. Hover, press and focus feedback come from the Button variant used by PaginationLink.
  • The component does not require a reduced-motion override.

Accessibility

  • Keep the root as a labelled nav and PaginationContent as a real list.
  • Set isActive only on the current page so aria-current="page" remains accurate.
  • Preserve the built-in previous, next and ellipsis screen-reader labels.

Implementation Guidance

  • Use inert links only in examples; production pagination should navigate or update controlled page state.
  • Interactive demos should update isActive from controlled state so previous, next and page controls demonstrate the real behavior.
  • Do not hand-build rows of links or buttons when the Pagination slots fit.
  • Keep aria-current, data-active, labels and Button integration intact.

On This Page