Skip to content

ComponentsNavigation

Pagination

Move between pages of results. Use the ready-made Paginator for state-driven lists with automatic ellipses, a compact "Page x of y" mode and two sizes, or compose link-based pagination where every page has its own URL. Exports the getPageRange helper behind the ellipsis logic.

Source

Installation

pnpm dlx shadcn@latest add @desyne/pagination

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { Paginator } from "@/components/ui/pagination";
tsx
const [page, setPage] = useState(1);

<Paginator page={page} pageCount={20} onPageChange={setPage} />;

For link-based pagination, where each page has its own URL, compose the parts:

tsx
import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";
tsx
<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationPrevious href="?page=1" />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink href="?page=2" isActive>2</PaginationLink>
    </PaginationItem>
    <PaginationItem>
      <PaginationNext href="?page=3" />
    </PaginationItem>
  </PaginationContent>
</Pagination>

When to use

  • Paginator — client-side state: tables, lists and search results where the page lives in React state and URLs don't need to change.
  • Link-based parts — server-rendered or routed lists where each page should be linkable, bookmarkable and crawlable (?page=3).
  • Simple mode — narrow spaces like card footers and mobile layouts, or very large page counts where numbers don't help.
  • Consider infinite scrolling or a "Load more" Button for feeds, where users browse rather than look for a specific page.

Anatomy

tsx
<Pagination>                 {/* <nav aria-label="pagination"> */}
  <PaginationContent>        {/* <ul> */}
    <PaginationItem>         {/* <li> */}
      <PaginationPrevious />
    </PaginationItem>
    <PaginationItem>
      <PaginationLink />     {/* page number */}
    </PaginationItem>
    <PaginationItem>
      <PaginationEllipsis /> {/* skipped pages */}
    </PaginationItem>
    <PaginationItem>
      <PaginationNext />
    </PaginationItem>
  </PaginationContent>
</Pagination>

<Paginator />                {/* all of the above, with buttons, in one component */}
PartRendersNotes
Pagination<nav aria-label="pagination">Landmark. Centered, full width by default.
PaginationContent<ul>Row of items with gap-1.
PaginationItem<li>Wraps one link, button or ellipsis.
PaginationLinkReact Aria LinkA page link styled with buttonVariants: ghost normally, outline with a primary border when isActive (which also sets aria-current="page").
PaginationPrevious / PaginationNextPaginationLinkChevron plus "Previous" / "Next" text. The text is hidden below the sm breakpoint.
PaginationEllipsis<span aria-hidden>"…" icon for skipped pages.
PaginatorPagination with buttonsPrevious and next icon buttons, page-number buttons from getPageRange(), or "Page x of y" in simple mode.

Examples

Sizes

md (32px, default) and sm (28px) for tables and dense toolbars.

Simple

simple replaces the page numbers with "Page x of y" between the arrows. It takes a fixed amount of space regardless of the page count.

Siblings

siblings sets how many pages are shown on each side of the current page (default 1). The first and last pages are always shown, and a gap becomes an ellipsis.

siblings=0
siblings=1
siblings=2

Few pages

When every page fits (pageCount up to siblings * 2 + 5, so 7 by default), all numbers are shown with no ellipsis. Previous is disabled on the first page and Next on the last.

Compose PaginationLink, PaginationPrevious, PaginationNext and PaginationEllipsis when each page is a URL. Mark the current page with isActive.

Link parts accept size="sm" or size="md" ("default" and "icon" are accepted as aliases for md).

With a router and getPageRange

Build the page list with getPageRange(page, pageCount, siblings?), which returns numbers and "ellipsis" markers, and disable Previous and Next at the ends with isDisabled. Wrap the app in React Aria's RouterProvider so the links use client-side navigation. The preview uses a local stand-in for the router.

With the Next.js App Router, read the page from the URL:

app/invoices/page.tsxtsx
import { InvoicePagination } from "./invoice-pagination";

export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
  const page = Number((await searchParams).page ?? 1);
  // …fetch the rows for `page`
  return <InvoicePagination page={page} pageCount={24} />;
}

InvoicePagination is a client component that renders the parts as in the preview, with an href of ?page=N for each page. Your app's RouterProvider passes router.push as navigate.

ts
getPageRange(5, 10); // [1, "ellipsis", 4, 5, 6, "ellipsis", 10]
getPageRange(2, 10); // [1, 2, 3, "ellipsis", 10]
getPageRange(2, 6); // [1, 2, 3, 4, 5, 6]

Recipes

A rows-per-page Select, a range summary and a small paginator under a Table. Changing the page size resets to page 1. Pass className="mx-0 w-auto" to stop the paginator from centering itself.

InvoiceCustomerStatusAmount
INV-1001Acme CorpPaid$480.00
INV-1002GlobexPending$5,105.00
INV-1003InitechOverdue$4,730.00
INV-1004UmbrellaPaid$4,355.00
INV-1005HooliPending$3,980.00
Rows per page
1–5 of 57

A compact simple paginator aligned to the end of a Card footer with className="justify-end".

Recent activity
Everything that happened in your workspace.
  • MC
    Maya Chen deployed acme-web to production2m ago
  • OF
    Omar Farouk merged #482 Add checkout retries18m ago
  • LP
    Lena Park invited 3 people to Growth1h ago

Accessibility

  • Rendered in a <nav aria-label="pagination"> landmark. Pass your own aria-label (for example "Search results pages") when a page has more than one.
  • The current page has aria-current="page".
  • Paginator labels its buttons "Previous page", "Next page" and "Page 3"; the link parts label Previous and Next "Go to previous page" and "Go to next page". Override any of them with aria-label.
  • Previous and Next are disabled (and removed from the tab order) at the ends.
  • Ellipses are aria-hidden.
  • After changing pages in client-side lists, consider moving focus to the top of the results or announcing the change, so screen-reader users know the content updated.

Keyboard

KeyAction
Tab / Shift+TabMoves between the controls
EnterActivates the focused page, Previous or Next
SpaceActivates a Paginator button (links respond to Enter only)

Styling

Data attributes

On PaginationLink, PaginationPrevious, PaginationNext and the Paginator buttons:

AttributePresent when
data-hovered / data-pressedHovered / being pressed
data-focus-visibleFocused with the keyboard
data-disabledDisabled
data-activeLink parts with isActive
data-currentLink parts with isActive (from aria-current)

Slots

data-slotElement
pagination<nav> root
pagination-content<ul>
pagination-itemEach <li>
pagination-linkEach link part
pagination-ellipsisEllipsis

Customizing

  • Items use buttonVariants, so they follow button tones and radii. Page numbers are tabular-nums so widths don't shift.
  • className on Pagination or Paginator styles the <nav>. It's centered with mx-auto flex w-full justify-center; use justify-start, justify-end or mx-0 w-auto to align it.
  • className on PaginationLink must be a string.

API Reference

Paginator

Prop

Type

Also accepts every prop of <nav> except onChange.

Prop

Type

Also accepts every prop of React Aria's Link except a function className.

PaginationPrevious / PaginationNext

Accept the same props as PaginationLink. Their content is fixed (chevron and label); aria-label defaults to "Go to previous page" / "Go to next page".

Pagination / PaginationContent / PaginationItem / PaginationEllipsis

Accept every prop of <nav> / <ul> / <li> / <span>.

getPageRange

Prop

Type

Returns (number | "ellipsis")[]: always the first and last page, the current page with its siblings, and "ellipsis" for each gap. When pageCount is at most siblings * 2 + 5, returns every page.

  • Table — the most common place for pagination.
  • Select — rows-per-page pickers.
  • Link — the link used by the link-based parts.
  • Button — buttonVariants styles every item.