Skip to content

Pagination

Navigation

Bounded pagination with compact page windows and result announcements.

Page window and ellipsesBounded navigationDisabled loading state
Open preview

Responsive viewport — resize the window to explore. Direction and density apply only to this preview. Theme applies to the docs too.

Usage

Pass a one-based page, totalPages, and onPageChange. The component displays the first, last, and nearby pages with ellipses. Load or filter data in your application; Pagination does not fetch records.

Import
import { Pagination } from "@/components/ui/pagination";

Shipped as editable source in src/components/ui/pagination.tsx in both apps. Copy the implementation below when moving it to another project. Copy the complete example from Source code for all imports and state. Add "use client" when using hooks or event handlers. See installation and theming for project setup.

Component source

The complete implementation is included in both apps. It uses the React, Radix, Lucide, and shared-core dependencies already installed in Apex. Keep your project’s semantic theme tokens when reusing it.

src/components/ui/pagination.tsx
"use client";
import { ChevronLeft, ChevronRight, Ellipsis } from "lucide-react";
import { Button } from "@dashboardpack/core/components/ui/button";
import { cn } from "@dashboardpack/core/lib/utils";
export interface PaginationProps {
  page: number;
  totalPages: number;
  onPageChange: (page: number) => void;
  disabled?: boolean;
  className?: string;
  label?: string;
}
export function Pagination({
  page,
  totalPages,
  onPageChange,
  disabled,
  className,
  label = "Pagination",
}: PaginationProps) {
  const total = Number.isFinite(totalPages)
    ? Math.max(1, Math.floor(totalPages))
    : 1;
  const current = Number.isFinite(page)
    ? Math.min(total, Math.max(1, Math.floor(page)))
    : 1;
  const pages = Array.from(
    new Set(
      [1, current - 1, current, current + 1, total].filter(
        (n) => n >= 1 && n <= total,
      ),
    ),
  ).sort((a, b) => a - b);
  return (
    <nav
      aria-label={label}
      className={cn("flex flex-wrap items-center gap-1", className)}
    >
      <Button
        type="button"
        size="icon-sm"
        variant="outline"
        aria-label="Previous page"
        disabled={disabled || current === 1}
        onClick={() => onPageChange(current - 1)}
      >
        <ChevronLeft className="size-4 rtl:rotate-180" />
      </Button>
      {pages.map((n, index) => (
        <span key={n} className="inline-flex items-center gap-1">
          {index > 0 && n - pages[index - 1] > 1 && (
            <span aria-hidden className="px-1">
              <Ellipsis className="size-4" />
            </span>
          )}
          <Button
            type="button"
            size="icon-sm"
            variant={n === current ? "default" : "ghost"}
            aria-label={`Page ${n}`}
            aria-current={n === current ? "page" : undefined}
            disabled={disabled}
            onClick={() => onPageChange(n)}
          >
            {n}
          </Button>
        </span>
      ))}
      <Button
        type="button"
        size="icon-sm"
        variant="outline"
        aria-label="Next page"
        disabled={disabled || current === total}
        onClick={() => onPageChange(current + 1)}
      >
        <ChevronRight className="size-4 rtl:rotate-180" />
      </Button>
      <span role="status" className="ms-2 text-xs text-muted-foreground">
        Page {current} of {total}
      </span>
    </nav>
  );
}

API reference

Common props and defaults. Slot-specific props are named below; native elements also accept their standard HTML attributes. The component TypeScript definitions are the complete type reference.

Pagination props
PropType / DefaultDescription
pagenumber

Default: required

One-based current page, visually clamped to valid bounds.
totalPagesnumber

Default: required

Page count; zero or invalid values display one page.
onPageChange(page: number) => void

Default: required

Receives the requested page.
disabledboolean

Default: false

Disable navigation while loading.
labelstring

Default: Pagination

Accessible navigation landmark name.

Build with these too