Pagination
A pagination with page navigation, next and previous links. Built with Base UI and Tailwind CSS. Copy-paste ready.
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
export default function Particle() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Installation
pnpm dlx shadcn@latest add @cnippet/pagination
Usage
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination"<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>Examples
Without Labels
Removes the "Previous" and "Next" text labels, showing only arrow icons for a more compact pagination bar.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink aria-label="Go to previous page" href="#" size="icon">
<ChevronLeftIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink aria-label="Go to next page" href="#" size="icon">
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
With Hover Effect
Adds a background highlight on hover for each page number link to improve click affordance.
"use client";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink
className="hover:border! hover:border-border"
href="#"
>
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink
className="hover:border! hover:border-border"
href="#"
>
3
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
With Circle Buttons
Renders each page number as a circular button for a rounder, more icon-centric pagination style.
import { cn } from "@/lib/utils";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious className="rounded-full" href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink className="rounded-full" href="#">
1
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className={cn("rounded-full")} href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="rounded-full" href="#">
3
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationNext className="rounded-full" href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
With Go-To-Page Input
Adds a text input that lets users jump directly to any page number without clicking through sequentially.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination className="w-full max-w-xs">
<PaginationContent className="justify-between gap-4">
<PaginationItem className="flex items-center gap-1">
<PaginationLink aria-label="Go to previous page" href="#" size="icon">
<ChevronLeftIcon className="size-4" />
</PaginationLink>
<PaginationLink href="#" isActive>
1
</PaginationLink>
<PaginationLink href="#">2</PaginationLink>
<PaginationLink href="#">3</PaginationLink>
<PaginationEllipsis />
<PaginationLink aria-label="Go to next page" href="#" size="icon">
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem className="flex items-center gap-2">
<span className="whitespace-nowrap text-muted-foreground text-sm">
Go to page
</span>
<Input
className="h-9 w-14 text-center"
defaultValue={1}
max={10}
min={1}
type="number"
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
In Card
Wraps pagination controls inside a Card alongside a row count indicator, suitable for table footers.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Card className="p-2">
<CardContent className="p-0">
<Pagination>
<PaginationContent>
{/* Previous */}
<PaginationItem>
<PaginationLink
aria-label="Go to previous page"
className="h-8 w-8 rounded-full hover:bg-muted"
href="#"
size="icon"
>
<ChevronLeftIcon className="size-4" />
</PaginationLink>
</PaginationItem>
{[1, 2, 3].map((page) => (
<PaginationItem key={page}>
<PaginationLink
className={
page === 1
? "bg-primary text-primary-foreground hover:bg-primary/90"
: "hover:bg-muted"
}
href="#"
isActive={page === 1}
>
{page}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
{[10, 11, 12].map((page) => (
<PaginationItem key={page}>
<PaginationLink href="#">{page}</PaginationLink>
</PaginationItem>
))}
{/* Next */}
<PaginationItem>
<PaginationLink
aria-label="Go to next page"
className="h-8 w-8 rounded-full hover:bg-muted"
href="#"
size="icon"
>
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
</CardContent>
</Card>
);
}
With Arrows Buttons
Uses larger arrow-only buttons with no visible page numbers for simple previous/next browsing.
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination className="w-full max-w-xs">
<PaginationContent className="w-full justify-between">
<PaginationItem>
<PaginationLink className="gap-2" href="#" size="default">
<ArrowLeftIcon className="size-4" />
Previous
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="gap-2" href="#" size="default">
Next
<ArrowRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Page Info Centered
Centers a "Page X of Y" text label between the previous and next controls.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination className="w-full max-w-xs">
<PaginationContent className="w-full justify-between">
<PaginationItem>
<PaginationLink aria-label="Go to previous page" href="#" size="icon">
<ChevronLeftIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<span className="text-muted-foreground text-xs">
Page <span className="font-medium text-foreground">1</span> of{" "}
<span className="font-medium text-foreground">10</span>
</span>
</PaginationItem>
<PaginationItem>
<PaginationLink aria-label="Go to next page" href="#" size="icon">
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Page Info Left
Aligns the page info string to the left with the previous/next controls pushed to the right.
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination className="w-full max-w-xs">
<PaginationContent className="w-full justify-between">
<PaginationItem>
<span className="text-muted-foreground text-xs">
Page <span className="font-medium text-foreground">1</span> of{" "}
<span className="font-medium text-foreground">10</span>
</span>
</PaginationItem>
<PaginationItem className="flex gap-1">
<PaginationPrevious href="#" />
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Outline Buttons
Renders page buttons with an outline border variant instead of the default ghost style for a more defined appearance.
//biome-ignore-all lint/suspicious/noArrayIndexKey: <>
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent className="gap-0 overflow-hidden rounded-md border">
<PaginationItem>
<PaginationLink
aria-label="Go to previous page"
className="rounded-none border-0 border-border border-e"
href="#"
size="icon"
>
<ChevronLeftIcon />
</PaginationLink>
</PaginationItem>
{
/* Page numbers */
Array.from({ length: 4 }).map((_, index) => (
<PaginationItem key={index}>
<PaginationLink
className="rounded-none border-0 border-border border-e data-[active=true]:bg-muted"
href="#"
isActive={index === 2}
>
{index + 1}
</PaginationLink>
</PaginationItem>
))
}
<PaginationItem className="border-0 border-border border-e">
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationLink
aria-label="Go to next page"
className="rounded-none border-0"
href="#"
size="icon"
>
<ChevronRightIcon />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
With Page Select
Adds a Select for choosing the current page and dedicated first/last page buttons for large datasets.
import {
ChevronFirstIcon,
ChevronLastIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink aria-label="Go to first page" href="#" size="icon">
<ChevronFirstIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink aria-label="Go to previous page" href="#" size="icon">
<ChevronLeftIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<select
className="w-26 rounded-lg border border-input bg-background px-2 py-1 text-foreground text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24"
defaultValue="1"
>
<option value="1">Page 1</option>
<option value="2">Page 2</option>
<option value="3">Page 3</option>
<option value="4">Page 4</option>
<option value="5">Page 5</option>
<option value="6">Page 6</option>
<option value="7">Page 7</option>
<option value="8">Page 8</option>
<option value="9">Page 9</option>
<option value="10">Page 10</option>
</select>
</PaginationItem>
<PaginationItem>
<PaginationLink aria-label="Go to next page" href="#" size="icon">
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink aria-label="Go to last page" href="#" size="icon">
<ChevronLastIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Numbered With Input
Combines numbered page links with an inline jump-to input field for large paginated datasets.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent className="w-full justify-between">
<PaginationItem className="flex items-center gap-1">
<PaginationLink aria-label="Go to previous page" href="#" size="icon">
<ChevronLeftIcon className="size-4" />
</PaginationLink>
<PaginationLink href="#" isActive>
1
</PaginationLink>
<PaginationLink href="#">2</PaginationLink>
<PaginationLink href="#">3</PaginationLink>
<PaginationLink href="#">4</PaginationLink>
<PaginationEllipsis />
<PaginationLink aria-label="Go to next page" href="#" size="icon">
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem className="flex items-center gap-2">
<span className="whitespace-nowrap text-muted-foreground text-sm">
Go to page
</span>
<Input
className="h-9 w-16 text-center"
defaultValue={1}
max={10}
min={1}
type="number"
/>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Full Featured
A full-featured pagination bar including total count, ellipsis collapse, and a rows-per-page select dropdown.
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent className="w-full justify-between">
<PaginationItem>
<span className="text-muted-foreground text-sm">
Page <span className="font-medium text-foreground">1</span> of{" "}
<span className="font-medium text-foreground">10</span>
</span>
</PaginationItem>
<PaginationItem className="flex items-center gap-1">
<PaginationPrevious href="#" />
<PaginationLink href="#" isActive>
1
</PaginationLink>
<PaginationLink href="#">2</PaginationLink>
<PaginationLink href="#">3</PaginationLink>
<PaginationLink href="#">4</PaginationLink>
<PaginationEllipsis />
<PaginationNext href="#" />
</PaginationItem>
<PaginationItem>
<select className="w-28 rounded-lg border border-input bg-background px-2 py-1 text-foreground text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/24">
<option value="10">10 / page</option>
<option value="20">20 / page</option>
<option value="50">50 / page</option>
<option value="100">100 / page</option>
</select>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Mini Pill
A compact rounded-full border wrapping previous/next arrows around a centered "X of Y" counter — minimal footprint for tight layouts.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent className="gap-0 overflow-hidden rounded-full border">
<PaginationItem>
<PaginationLink
aria-label="Go to previous page"
className="rounded-none border-0 border-e"
href="#"
size="icon"
>
<ChevronLeftIcon className="size-4" />
</PaginationLink>
</PaginationItem>
<PaginationItem>
<span className="flex items-center px-4 text-muted-foreground text-sm">
<span className="font-medium text-foreground">3</span> of 12
</span>
</PaginationItem>
<PaginationItem>
<PaginationLink
aria-label="Go to next page"
className="rounded-none border-0 border-s"
href="#"
size="icon"
>
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Older / Newer Navigation
Blog-style cursor navigation with "← Newer posts" and "Older posts →" links instead of numbered pages — ideal for chronological feeds.
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination>
<PaginationContent className="gap-2">
<PaginationItem>
<PaginationLink className="gap-1.5" href="#">
<ArrowLeftIcon aria-hidden="true" className="size-4" />
Newer posts
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink className="gap-1.5" href="#">
Older posts
<ArrowRightIcon aria-hidden="true" className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Controlled with Smart Ellipsis
A useState-driven pagination that recomputes the visible page window on every click, collapsing distant pages into PaginationEllipsis automatically.
"use client";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import type React from "react";
import { useState } from "react";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
const TOTAL_PAGES = 20;
function getVisiblePages(current: number, total: number): (number | "...")[] {
if (total <= 7) return Array.from({ length: total }, (_, i) => i + 1);
if (current <= 4) return [1, 2, 3, 4, 5, "...", total];
if (current >= total - 3)
return [1, "...", total - 4, total - 3, total - 2, total - 1, total];
return [1, "...", current - 1, current, current + 1, "...", total];
}
export function Pattern() {
const [page, setPage] = useState(5);
const pages = getVisiblePages(page, TOTAL_PAGES);
function go(e: React.MouseEvent, p: number) {
e.preventDefault();
setPage(p);
}
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationLink
aria-disabled={page === 1}
aria-label="Go to previous page"
className={page === 1 ? "pointer-events-none opacity-50" : ""}
href="#"
onClick={(e) => go(e, page - 1)}
size="icon"
>
<ChevronLeftIcon className="size-4" />
</PaginationLink>
</PaginationItem>
{pages.map((p, idx) =>
p === "..." ? (
// biome-ignore lint/suspicious/noArrayIndexKey: ellipsis placeholders have no stable key
<PaginationItem key={`ellipsis-${idx}`}>
<PaginationEllipsis />
</PaginationItem>
) : (
<PaginationItem key={p}>
<PaginationLink
href="#"
isActive={p === page}
onClick={(e) => go(e, p as number)}
>
{p}
</PaginationLink>
</PaginationItem>
),
)}
<PaginationItem>
<PaginationLink
aria-disabled={page === TOTAL_PAGES}
aria-label="Go to next page"
className={
page === TOTAL_PAGES ? "pointer-events-none opacity-50" : ""
}
href="#"
onClick={(e) => go(e, page + 1)}
size="icon"
>
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Results Summary
A two-column bar with "Showing 41–60 of 230 results" on the left and compact prev/next arrows flanking a "3 / 12" counter on the right.
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<Pagination className="w-full">
<PaginationContent className="w-full justify-between">
<PaginationItem>
<p className="text-muted-foreground text-sm">
Showing <span className="font-medium text-foreground">41–60</span>{" "}
of <span className="font-medium text-foreground">230</span> results
</p>
</PaginationItem>
<PaginationItem className="flex items-center gap-1">
<PaginationLink aria-label="Go to previous page" href="#" size="icon">
<ChevronLeftIcon className="size-4" />
</PaginationLink>
<span className="text-muted-foreground text-sm tabular-nums">
3 / 12
</span>
<PaginationLink aria-label="Go to next page" href="#" size="icon">
<ChevronRightIcon className="size-4" />
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
}
Loading State
The entire pagination bar is dimmed with pointer-events-none opacity-40 and overlaid with a spinning Loader2Icon to communicate that results are being fetched.
import { Loader2Icon } from "lucide-react";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
export function Pattern() {
return (
<div className="relative">
<Pagination className="pointer-events-none opacity-40">
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
<div className="absolute inset-0 flex items-center justify-center">
<Loader2Icon
aria-label="Loading results"
className="size-4 animate-spin text-muted-foreground"
/>
</div>
</div>
);
}
On This Page

