Creative Tim UICreative Tim UI

Shadcn Pagination

PreviousNext

Navigate multi-page data sets with responsive pagination controls that blend seamlessly with your shadcn/ui project.

"use client"

import * as React from "react"

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

const TOTAL_PAGES = 10

export function PaginationDemo() {
  const [currentPage, setCurrentPage] = React.useState(2)

  const pages = React.useMemo(() => {
    const base = new Set<number>([1, TOTAL_PAGES])
    for (let delta = -1; delta <= 1; delta += 1) {
      const candidate = currentPage + delta
      if (candidate > 1 && candidate < TOTAL_PAGES) {
        base.add(candidate)
      }
    }

    return Array.from(base).sort((a, b) => a - b)
  }, [currentPage])

  const goToPage = React.useCallback(
    (page: number) => {
      setCurrentPage(Math.min(Math.max(page, 1), TOTAL_PAGES))
    },
    [setCurrentPage]
  )

  const renderPageItems = () => {
    const items: React.ReactNode[] = []
    let lastPage: number | null = null

    pages.forEach((page) => {
      if (lastPage !== null && page - lastPage > 1) {
        items.push(
          <PaginationItem key={`ellipsis-${page}`}>
            <PaginationEllipsis />
          </PaginationItem>
        )
      }

      items.push(
        <PaginationItem key={page}>
          <PaginationLink
            href="#"
            isActive={page === currentPage}
            onClick={(event) => {
              event.preventDefault()
              goToPage(page)
            }}
          >
            {page}
          </PaginationLink>
        </PaginationItem>
      )

      lastPage = page
    })

    return items
  }

  const isFirstPage = currentPage === 1
  const isLastPage = currentPage === TOTAL_PAGES

  return (
    <Pagination className="justify-start md:justify-center">
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious
            href="#"
            className={cn(isFirstPage && "pointer-events-none opacity-40")}
            aria-disabled={isFirstPage}
            tabIndex={isFirstPage ? -1 : undefined}
            onClick={(event) => {
              event.preventDefault()
              if (!isFirstPage) {
                goToPage(currentPage - 1)
              }
            }}
          />
        </PaginationItem>
        {renderPageItems()}
        <PaginationItem>
          <PaginationNext
            href="#"
            className={cn(isLastPage && "pointer-events-none opacity-40")}
            aria-disabled={isLastPage}
            tabIndex={isLastPage ? -1 : undefined}
            onClick={(event) => {
              event.preventDefault()
              if (!isLastPage) {
                goToPage(currentPage + 1)
              }
            }}
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}

Installation

pnpm dlx @creative-tim/ui@latest add pagination

Usage

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination"
 
export default function Example() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#" isActive>
            2
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}

Examples

Pagination Demo

Classic pagination with previous/next buttons, current page highlighting, and ellipsis for long page ranges.

"use client"

import * as React from "react"

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

const TOTAL_PAGES = 10

export function PaginationDemo() {
  const [currentPage, setCurrentPage] = React.useState(2)

  const pages = React.useMemo(() => {
    const base = new Set<number>([1, TOTAL_PAGES])
    for (let delta = -1; delta <= 1; delta += 1) {
      const candidate = currentPage + delta
      if (candidate > 1 && candidate < TOTAL_PAGES) {
        base.add(candidate)
      }
    }

    return Array.from(base).sort((a, b) => a - b)
  }, [currentPage])

  const goToPage = React.useCallback(
    (page: number) => {
      setCurrentPage(Math.min(Math.max(page, 1), TOTAL_PAGES))
    },
    [setCurrentPage]
  )

  const renderPageItems = () => {
    const items: React.ReactNode[] = []
    let lastPage: number | null = null

    pages.forEach((page) => {
      if (lastPage !== null && page - lastPage > 1) {
        items.push(
          <PaginationItem key={`ellipsis-${page}`}>
            <PaginationEllipsis />
          </PaginationItem>
        )
      }

      items.push(
        <PaginationItem key={page}>
          <PaginationLink
            href="#"
            isActive={page === currentPage}
            onClick={(event) => {
              event.preventDefault()
              goToPage(page)
            }}
          >
            {page}
          </PaginationLink>
        </PaginationItem>
      )

      lastPage = page
    })

    return items
  }

  const isFirstPage = currentPage === 1
  const isLastPage = currentPage === TOTAL_PAGES

  return (
    <Pagination className="justify-start md:justify-center">
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious
            href="#"
            className={cn(isFirstPage && "pointer-events-none opacity-40")}
            aria-disabled={isFirstPage}
            tabIndex={isFirstPage ? -1 : undefined}
            onClick={(event) => {
              event.preventDefault()
              if (!isFirstPage) {
                goToPage(currentPage - 1)
              }
            }}
          />
        </PaginationItem>
        {renderPageItems()}
        <PaginationItem>
          <PaginationNext
            href="#"
            className={cn(isLastPage && "pointer-events-none opacity-40")}
            aria-disabled={isLastPage}
            tabIndex={isLastPage ? -1 : undefined}
            onClick={(event) => {
              event.preventDefault()
              if (!isLastPage) {
                goToPage(currentPage + 1)
              }
            }}
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}

Pagination Circular

Rounded pill pagination that keeps controls compact while staying touch-friendly—great for dashboards and mobile layouts.

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

export function PaginationCircular() {
  return (
    <Pagination className="justify-start md:justify-center">
      <PaginationContent className="gap-2">
        <PaginationItem>
          <PaginationPrevious
            href="#"
            className="bg-secondary/10 text-secondary-foreground hover:bg-secondary/20 rounded-full px-4"
          />
        </PaginationItem>
        {[1, 2, 3, 4, 5].map((page) => (
          <PaginationItem key={page}>
            <PaginationLink
              href="#"
              className="border-border rounded-full border px-4 py-2 text-sm font-medium"
              isActive={page === 2}
            >
              {page}
            </PaginationLink>
          </PaginationItem>
        ))}
        <PaginationItem>
          <PaginationNext
            href="#"
            className="bg-secondary/10 text-secondary-foreground hover:bg-secondary/20 rounded-full px-4"
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}

Pagination Group

Grouped pagination buttons with shared borders that feel like a segmented control for precise navigation.

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

export function PaginationGroup() {
  return (
    <Pagination className="justify-start md:justify-center">
      <PaginationContent className="bg-background overflow-hidden rounded-lg border shadow-sm">
        <PaginationItem>
          <PaginationPrevious
            href="#"
            className="rounded-none border-r px-5 text-sm font-medium"
          />
        </PaginationItem>
        {[1, 2, 3, 4, 5].map((page) => (
          <PaginationItem key={page}>
            <PaginationLink
              href="#"
              className="rounded-none border-x px-5 text-sm font-semibold"
              isActive={page === 3}
            >
              {page}
            </PaginationLink>
          </PaginationItem>
        ))}
        <PaginationItem>
          <PaginationNext
            href="#"
            className="rounded-none border-l px-5 text-sm font-medium"
          />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  )
}

Simple Pagination

Minimal pagination bar with icon buttons and a textual page indicator—perfect for tables or compact layouts.

Page 2 of 10

import { ChevronLeft, ChevronRight } from "lucide-react"

import { Button } from "@/components/ui/button"

export function SimplePagination() {
  return (
    <div className="flex items-center gap-3 rounded-full border px-4 py-2 shadow-sm">
      <Button
        variant="ghost"
        size="icon"
        className="border-border/60 text-muted-foreground hover:text-foreground rounded-full border"
      >
        <ChevronLeft className="h-4 w-4" />
      </Button>
      <p className="text-muted-foreground text-sm">
        Page{" "}
        <span className="text-foreground font-semibold" aria-live="polite">
          2
        </span>{" "}
        of <span className="text-foreground font-semibold">10</span>
      </p>
      <Button
        variant="ghost"
        size="icon"
        className="border-border/60 text-muted-foreground hover:text-foreground rounded-full border"
      >
        <ChevronRight className="h-4 w-4" />
      </Button>
    </div>
  )
}

Props

Pagination

PropTypeDefaultDescription
classNamestring-Additional classes for the <nav> tag.
...propsReact.ComponentProps<"nav">-Native nav props such as role.

PaginationContent

PropTypeDefaultDescription
classNamestring-Additional classes for the <ul> wrapper.
...propsReact.ComponentProps<"ul">-Native list props.
PropTypeDefaultDescription
hrefstring-Destination for the link.
isActivebooleanfalseControls the active/outlined state.
size"default" | "icon""icon"Matches the shadcn Button sizes.
classNamestring-Additional classes for styling.
...propsReact.ComponentProps<"a">-Anchor props like target or rel.

PaginationPrevious / PaginationNext

PropTypeDefaultDescription
classNamestring-Extra classes for styling.
...propsReact.ComponentProps<typeof PaginationLink>-Accept the same props as PaginationLink.

PaginationEllipsis

PropTypeDefaultDescription
classNamestring-Tailor spacing or color of the ellipsis dots.
...propsReact.ComponentProps<"span">-Native span props.

Best Practices

  • Use ellipsis to keep long page lists concise and scannable.
  • Pair pagination with results metadata (e.g., "241 results") so users understand context.
  • Keep hit targets large enough for touch devices—circular or pill variants are great here.
  • Disable previous/next links when users reach the ends of the collection.
  • Provide keyboard focus styles so pagination stays accessible.
  • Use descriptive aria-label attributes when pagination controls repeat across the page.

Pagination keeps big data sets manageable. Combine these controls with tables, cards, or galleries to give users confident navigation without overwhelming them.