Siteplane UI

Pagination

Move between pages of a long list or table.

Markdown
"use client";

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

const totalPages = 10;

type PageItem = number | "start-ellipsis" | "end-ellipsis";

// Seven slots at every page, so the control keeps its width while it pages.
function getPageItems(page: number, total: number): PageItem[] {
  if (page <= 4) return [1, 2, 3, 4, 5, "end-ellipsis", total];
  if (page >= total - 3) {
    return [
      1,
      "start-ellipsis",
      total - 4,
      total - 3,
      total - 2,
      total - 1,
      total,
    ];
  }
  return [1, "start-ellipsis", page - 1, page, page + 1, "end-ellipsis", total];
}

export default function Example() {
  const [page, setPage] = useState(5);

  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious
            disabled={page === 1}
            onClick={() => setPage(page - 1)}
          />
        </PaginationItem>
        {getPageItems(page, totalPages).map((item) => (
          <PaginationItem key={item}>
            {typeof item === "number" ? (
              <PaginationLink
                active={item === page}
                onClick={() => setPage(item)}
              >
                {item}
              </PaginationLink>
            ) : (
              <PaginationEllipsis />
            )}
          </PaginationItem>
        ))}
        <PaginationItem>
          <PaginationNext
            disabled={page === totalPages}
            onClick={() => setPage(page + 1)}
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/pagination

Complete the one-time setup first. Registry JSON lists source files and dependencies.

Usage

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
} from "@/siteplane/components/pagination";

<Pagination>
  <PaginationContent>
    <PaginationItem>
      <PaginationLink active>1</PaginationLink>
    </PaginationItem>
  </PaginationContent>
</Pagination>;

Composition

Pagination
└── PaginationContent
    └── PaginationItem
        └── PaginationPrevious / PaginationLink / PaginationNext / PaginationEllipsis

Compact navigation

variant="ghost" hides the Previous and Next text and keeps icon buttons with their accessible names. Use it where space is tight, for example below a table.

"use client";

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

const pages = [1, 2, 3, 4, 5];

export default function Example() {
  const [page, setPage] = useState(3);

  return (
    <Pagination variant="ghost">
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious
            disabled={page === 1}
            onClick={() => setPage(page - 1)}
          />
        </PaginationItem>
        {pages.map((number) => (
          <PaginationItem key={number}>
            <PaginationLink
              active={page === number}
              onClick={() => setPage(number)}
            >
              {number}
            </PaginationLink>
          </PaginationItem>
        ))}
        <PaginationItem>
          <PaginationNext
            disabled={page === pages.length}
            onClick={() => setPage(page + 1)}
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

API reference

Component properties
PropTypeDefaultDescription
Pagination.variantdefault | ghostdefaultDirectional navigation display.
Pagination.animatebooleantrueActive indicator motion; reduced motion is respected.
Pagination.labelsPartial<PaginationLabels>EnglishReplaces the words of the parts, each one on its own: navigation ("pagination", the landmark name), previous ("Previous"), previousPage ("Go to previous page"), next ("Next"), nextPage ("Go to next page") and morePages ("More pages"). Pass their translation when the application is in another language.
PaginationContent / PaginationItemul / li props—Content measures the active item; item supplies list semantics.
PaginationLink.activebooleanfalseSets aria-current="page" and the active indicator. isActive is a deprecated alias.
PaginationLink.sizeButtonStandardProps["size"]Icon size from providerExplicit button size override. A page link is at least square, and a longer number widens it with 4 px on each side.
PaginationLink render / disabled / onClickButtonStandardPropsghost buttonApplication owns navigation and disabled boundaries.
PaginationPrevious / PaginationNextButtonBaseProps & { active?: boolean }size: provider / lgDirectional buttons with fixed icon and label from Pagination labels; default variant responds to viewport width. The chevrons mirror in a right-to-left page.
PaginationEllipsisspan props—Decorative gap, not a navigation action.

Exported types: PaginationProps, PaginationLinkProps, PaginationLabels. These describe the props above.

Read the installed source.

Usage notes

No page count, slicing, URL state, or data fetching is built in. Set those in the application. The examples keep the current page in state: active marks the current link, onClick changes the page, and disabled stops Previous and Next at the first and last page. They also decide which page numbers to show; the first example always renders seven slots, so the control keeps its width while it pages. Root accepts native nav props. Link and direction controls inherit the corresponding Button API.

A page link keeps the square of its size for a short number. A longer one, such as 1,248, widens the link with 4 px on each side instead of filling the square edge to edge, and the active indicator follows its width. Format the numbers for the application's locale; the links show the text you pass.

labels replaces the words that Pagination renders, merged with the English defaults, for example labels={{ previous: "Zurück", previousPage: "Zur vorherigen Seite", next: "Weiter", nextPage: "Zur nächsten Seite" }}. An aria-label on PaginationPrevious or PaginationNext still replaces its accessible name. In a right-to-left page Previous sits on the right and Next on the left, and their chevrons point outward.