Pagination
Page numbers with ellipses and a gliding current-page indicator.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/pagination.jsonShowing 101–120 of 480
components/examples/pagination-demo.tsx
"use client";
import * as React from "react";
import { Pagination } from "@/components/ui/pagination";
export default function PaginationDemo() {
const [page, setPage] = React.useState(6);
return (
<div className="flex flex-col items-center gap-3">
<Pagination page={page} pageCount={24} onPageChange={setPage} />
<p className="text-sm text-muted-foreground">Showing {page * 20 - 19}–{page * 20} of 480</p>
</div>
);
}When to use
- Paging through tables and lists with a known number of pages.
When not to use
- Feeds: use infinite scroll or Load more.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/pagination.json1Install the dependencies
npm install motion lucide-react2Copy the source into your project
components/ui/pagination.tsx"use client"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { motion } from "motion/react"; import * as React from "react"; import { cn } from "@/lib/utils"; export interface PaginationProps { page: number; pageCount: number; onPageChange: (page: number) => void; /** Pages shown on each side of the current one. */ siblings?: number; className?: string; } function range(page: number, count: number, siblings: number): (number | "…")[] { const total = siblings * 2 + 5; if (count <= total) return Array.from({ length: count }, (_, i) => i + 1); const left = Math.max(page - siblings, 2); const right = Math.min(page + siblings, count - 1); return [1, ...(left > 2 ? ["…" as const] : []), ...Array.from({ length: right - left + 1 }, (_, i) => left + i), ...(right < count - 1 ? ["…" as const] : []), count]; } export function Pagination({ page, pageCount, onPageChange, siblings = 1, className }: PaginationProps) { const id = React.useId(); const btn = "grid h-8 min-w-8 place-items-center rounded-md px-2 text-sm tabular-nums transition-colors disabled:pointer-events-none disabled:opacity-40"; return ( <nav aria-label="Pagination" className={cn("flex items-center gap-1", className)}> <button type="button" aria-label="Previous page" disabled={page <= 1} onClick={() => onPageChange(page - 1)} className={cn(btn, "hover:bg-accent")}> <ChevronLeft className="size-4" /> </button> {range(page, pageCount, siblings).map((p, i) => p === "…" ? ( <span key={`e${i}`} className="px-1 text-sm text-muted-foreground" aria-hidden>…</span> ) : ( <button key={p} type="button" aria-current={p === page ? "page" : undefined} onClick={() => onPageChange(p)} className={cn(btn, "relative", p === page ? "text-primary-foreground" : "hover:bg-accent")}> {p === page && <motion.span layoutId={`${id}-page`} className="absolute inset-0 rounded-md bg-primary" transition={{ type: "spring", bounce: 0.15, duration: 0.35 }} />} <span className="relative">{p}</span> </button> ), )} <button type="button" aria-label="Next page" disabled={page >= pageCount} onClick={() => onPageChange(page + 1)} className={cn(btn, "hover:bg-accent")}> <ChevronRight className="size-4" /> </button> </nav> ); }
Usage
Import it from @/components/ui/pagination after installing.
example.tsx
import * as React from "react";
import { Pagination } from "@/components/ui/pagination";
export function Pages() {
const [page, setPage] = React.useState(1);
return <Pagination page={page} pageCount={24} onPageChange={setPage} />;
}API reference
Pagination
Previous, next and page numbers with ellipses.
| Prop | Type | Default | Description |
|---|---|---|---|
| page | number | – | Current page, 1-based. |
| pageCount | number | – | Total pages. |
| onPageChange | (page: number) => void | – | Called with the new page. |
| siblings | number | 1 | Pages shown each side of the current one. |
Keyboard interactions
| Keys | Action |
|---|---|
| Tab | Move between page buttons |
| EnterorSpace | Go to the page |
Accessibility
- nav with aria-label; the current page has aria-current="page".
- Previous and next are labelled.
Motion
- The current-page highlight slides between numbers.
Responsive behavior
- Use siblings={0} on phones.
Performance
- Renders at most about nine buttons.
Notes for AI
- Pages are 1-based.
This page as Markdown, for assistants and coding agents:
/components/pagination/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt