Getting Started
App Providerglobal.d.tsThemesTypes
cn(...args)Text ParserUnits Converters
AnchorAvatarBreadcrumbBurgerButtonCardCarouselCheckerCodeColor PickerCommandConfettiCopyButtonDouble Helix WordsFloating IndicatorGroupHighlight TextIndicatorInputKbdLabelLoaderPaginationPassword RequirementPolymorphic SlotProgressProseRatingRunning AreaScroll AreaSheetsSkeletonSliderStackSvgTableTabsTextareaTimelineTimesToasterTooltipTyping WordsTypography
useClickOutsideuseClipboarduseDeviceInfouseDialoguseDidUpdateuseDirectionuseDisclosureuseDocumentTitleuseDocumentVisibilityuseElementInfouseEyeDropperuseFetchuseFullscreenuseGeoLocationuseHotkeysuseHoveruseIduseImagePopupuseInputStateuseIntersectionuseIntervaluseIsomorphicEffectuseListStateuseLocalStorageuseMeasureScrollbaruseMediaQueryuseMergedRefuseMouseuseMoveuseMutationObserveruseNetworkuseOpenStateuseOrientationuseOSusePaginationusePWAInstalleruseRandomColorsuseReducedMotionuseReloaduseResizeObserveruseScrollIntoViewuseStateHistoryuseTimeoutuseTouchuseTriggeruseUncontrolleduseValidatedStateuseViewportSizeuseWindowEventuseWindowScroll
Docs
Web
Components
Pagination
Loader

A loading indicator component used to show that content or data is being loaded or processed.

Password Requirement

Shows live validation feedback for password requirements.


Edit this page on GitHub
  • Started
  • Utilities
  • Configuration
  • Components
  • Hooks
  • Examples
  • Github
  • Contributing
⌘+J

© 2025 oeri rights MIT


Designed in Earth-616

Built by oeri

Pagination

A navigation component for splitting content into multiple pages, often used in tables, lists, or galleries.

Usage

Basic usage example to quickly see how the Pagination component works. This demonstration provides a quick overview of how the pagination mechanism is implemented and interacted with.

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

export function PaginationDemo() {
  return <Pagination total={16} defaultValue={11} />;
}

Properties

Interactive configurator to explore various customization options for the Pagination component. You can adjust settings such as the total number of pages, current active page, boundary counts, sibling counts, and more.

Sibling
Boundaries
Color
import { Pagination } from "@/ui/pagination";

export function PaginationDemo() {
  return <Pagination total={16} defaultValue={11} />;
}

Pagination As Link

Demonstration of rendering pagination items as links. This example shows how pagination buttons can be transformed into navigable links, useful for applications with route-based navigation (e.g., Next.js routing).

Regular Component
1
789
15
Composite Component
12345
10
"use client";
import React from "react";
import { Typography } from "@/ui/typography";
import { Pagination, PaginationFirst, PaginationItems, PaginationLast, PaginationNext, PaginationPrevious } from "@/ui/pagination";

export function PaginationAsLinkDemo() {
  const [activePage, setPage] = React.useState(8);
  const lastPage = 15;

  const getHrefForControl = (control: string, currentPage: number): string => {
    switch (control) {
      case "first":
        return `#page-1`;
      case "previous":
        return `#page-${Math.max(1, currentPage - 1)}`;
      case "next":
        return `#page-${Math.min(lastPage, currentPage + 1)}`;
      case "last":
        return `#page-${lastPage}`;
      default:
        return "#";
    }
  };
  return (
    <div className="m-auto size-full">
      <Typography prose="span">Regular Component</Typography>
      <Pagination
        withEdges
        total={lastPage}
        value={activePage}
        onChange={page => setTimeout(() => setPage(page), 150)}
        getItemProps={page => ({
          el: "a",
          href: `#page-${page}`
        })}
        getControlProps={control => ({
          el: "a",
          href: getHrefForControl(control, activePage)
        })}
      />

      <Typography prose="span">Composite Component</Typography>
      <Pagination
        total={10}
        getItemProps={page => ({
          el: "a",
          href: `#page-${page}`
        })}
      >
        <PaginationFirst el="a" href="#page-0" />
        <PaginationPrevious el="a" href="#page-1" />
        <PaginationItems />
        <PaginationNext el="a" href="#page-2" />
        <PaginationLast el="a" href="#page-10" />
      </Pagination>
    </div>
  );
}

Pagination Change Icons

Customize the pagination navigation icons. Here you can see how to replace the default navigation icons (previous, next, first, last) with custom icons according to your design needs.

Regular Component
Composite Component
import React from "react";
import { Pagination } from "@/ui/pagination";
import { Typography } from "@/ui/typography";
import { ArrowSquareRoundRightIcon, ArrowSquareRoundLeftIcon, ChevronCircleRightIcon, ChevronCircleLeftIcon } from "@/icons/*";
import { Svg } from "@/ui/svg";

function Dot() {
  return (
    <Svg>
      <circle cx="12.1" cy="12.1" r="1" />
    </Svg>
  );
}

export function PaginationChangeIconsDemo() {
  return (
    <div className="m-auto size-full">
      <Typography prose="span">Regular Component</Typography>
      <Pagination
        siblings={2}
        boundaries={2}
        withEdges
        total={15}
        defaultValue={8}
        icons={{
          first: ArrowSquareRoundLeftIcon,
          previous: ChevronCircleLeftIcon,
          next: ChevronCircleRightIcon,
          last: ArrowSquareRoundRightIcon,
          dots: Dot
        }}
      />

      <Typography prose="span">Composite Component</Typography>
      <Pagination siblings={2} boundaries={2} total={15} defaultValue={8}>
        <Pagination.First icon={ArrowSquareRoundLeftIcon} />
        <Pagination.Previous icon={ChevronCircleLeftIcon} />
        <Pagination.Items dotsIcon={Dot} />
        <Pagination.Next icon={ChevronCircleRightIcon} />
        <Pagination.Last icon={ArrowSquareRoundRightIcon} />
      </Pagination>
    </div>
  );
}

Pagination With Chunked Content

Paginate dynamically loaded or chunked content. This example demonstrates how to divide a large dataset or long content into smaller pages, providing a smoother user experience with pagination controls.

"use client";
import { useState } from "react";
import { Pagination } from "@/ui/pagination";
import { dataRenderer, Table } from "@/ui/table";
import { Stack } from "@/ui/stack";

function randomId() {
  return `${Math.random().toString(36).slice(2, 11)}`;
}
function chunk<T>(array: T[], size: number): T[][] {
  if (!array.length) {
    return [];
  }
  const head = array.slice(0, size);
  const tail = array.slice(size);
  return [head, ...chunk(tail, size)];
}

const data = chunk(
  Array(30)
    .fill(0)
    .map((_, index) => ({ id: index, name: randomId() })),
  5
);

export function PaginationWithChunkedContentDemo() {
  const [activePage, setPage] = useState(1);

  const tableData = {
    head: ["ID", "NAME"],
    body: dataRenderer(data[activePage - 1], [
      (item) => item.id + 1,
      (item) => item.name,
    ])
  }

  return (
    <Stack>
      <Table data={tableData} />
      <Pagination total={data.length} value={activePage} onChange={setPage} />
    </Stack>
  );
}

Controlled

To control component state provide value and onChange props:

"use client";
import { useState } from "react";
import { Pagination } from "@/ui/pagination";
 
function ControlledPagination() {
  const [activePage, setPage] = useState(1);
  return <Pagination value={activePage} onChange={setPage} total={10} />;
}

Structure

Independent Components

import { Pagination, PaginationFirst, PaginationItems, PaginationLast, PaginationNext, PaginationPrevious } from "@/ui/pagination";
 
export function PaginationDemo() {
  const itemsProps = (page: number) => ({ el: "a", href: `#page-${page}` });
  return (
    <Pagination total={10} getItemProps={itemsProps}>
      <PaginationFirst el="a" href="#page-0" />
      <PaginationPrevious el="a" href="#page-1" />
      <PaginationItems />
      <PaginationNext el="a" href="#page-2" />
      <PaginationLast el="a" href="#page-10" />
    </Pagination>
  );
}

Compound Components

"use client";
import { Pagination } from "@/ui/pagination";
import { ArrowSquareRoundRightIcon, ArrowSquareRoundLeftIcon, ChevronCircleRightIcon, ChevronCircleLeftIcon, Svg } from "@/icons/*";
 
export function PaginationDemo() {
  return (
    <Pagination siblings={2} boundaries={2} total={15} defaultValue={8}>
      <Pagination.First icon={ArrowSquareRoundLeftIcon} />
      <Pagination.Previous icon={ChevronCircleLeftIcon} />
      <Pagination.Items dotsIcon={Dot} />
      <Pagination.Next icon={ChevronCircleRightIcon} />
      <Pagination.Last icon={ArrowSquareRoundRightIcon} />
    </Pagination>
  );
}

Declarative Props API

import { Pagination } from "@/ui/pagination";
 
export function PaginationDemo() {
  return <Pagination siblings={2} boundaries={2} withEdges color="orange" total={21} defaultValue={11} />;
}

API References

Styles API

type T = "root" | "control" | "dots";
Styles APITypeDefaultAnnotation
unstyled?Partial<Record<T, boolean>>falseif true, default styles will be removed
className?stringundefinedpass to root component <div>
classNames?Partial<Record<T, string>>undefined
style?CSSPropertiesundefinedpass to root component <div>
styles?Partial<Record<T, CSSProperties>>undefined

Props API

type Icons = { first?: PaginationIcon; previous?: PaginationIcon; next?: PaginationIcon; last?: PaginationIcon; dots?: PaginationIcon };
 
type Control = "first" | "previous" | "next" | "last";
Props APITypeDefaultAnnotation
icons?IconsdefaultControl icons component
gap?string| number8Key of gap between controls
size?(string & {}) | number32height and min-width of controls
totalnumber-Total number of pages, must be an integer
color?Property.ColorundefinedKey of `colors, active item color
value?numberundefinedActive page for controlled component, must be an integer in [0, total] interval
defaultValue?numberundefinedActive page for uncontrolled component, must be an integer in [0, total] interval
disabled?booleanfalseDetermines whether all controls should be disabled
hideWithOnePage?booleanfalseDetermines whether pagination should be hidden when only one page is available total={1}
withEdges?booleanfalseDetermines whether first/last controls should be rendered
withControls?booleantrueDetermines whether next/previous controls should be rendered
siblings?number1Number of siblings displayed on the left/right side of the selected page
boundaries?number1Number of elements visible on the left/right edges
onNextPage?() => voidundefinedCalled when next page control is clicked
onPreviousPage?() => voidundefinedCalled when previous page control is clicked
onFirstPage?() => voidundefinedCalled when first page control is clicked
onLastPage?() => voidundefinedCalled when last page control is clicked
onChange?(page: number) => voidundefinedCalled when page changes
getItemProps?(page: number) => Record<string, any>undefinedAdditional props passed down to controls
getControlProps?(control: Control) => Record<string, any>undefinedAdds props to next/previous/first/last controls

Source Codes

Full working code example, including necessary markup and styles. You can copy and paste this code directly to start using the component immediately.

pagination.tsx
globals.css
/* pagination */ @layer base { .stylelayer-pagination { & .pagination-control { @apply flex size-[--control-size] min-h-[--control-size] min-w-[--control-size] cursor-pointer appearance-none items-center justify-center rounded-md border bg-background p-1 text-[clamp(0.75rem,0.65rem+0.65vw,0.9rem)] font-medium leading-none text-muted-foreground transition-[transform,color,border-color,text-decoration-color,fill,stroke] duration-75 [-moz-appearance:none] [-webkit-appearance:none] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-muted-foreground/35 focus-visible:ring-offset-1 focus-visible:ring-offset-background active:scale-[.975] disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-50 [@media(hover:hover)]:hover:bg-muted/50 [@media(hover:hover)]:hover:text-color [&>svg]:size-[calc(var(--control-size)/1.8)]; &.active { @apply border-[--active-bg,hsl(var(--primitive-emphasis))] bg-[--active-bg,hsl(var(--primitive-foreground)/0.9)] text-[--active-color,hsl(var(--color))] [@media(hover:hover)]:hover:bg-[--active-bg,hsl(var(--primitive-foreground)/0.5)] focus-visible:[--tw-ring-color:--active-bg,hsl(var(--primitive-foreground)/0.35)]; } } & .pagination-dots { @apply flex items-center justify-center pointer-events-none my-auto text-muted-foreground shrink-0 h-[--control-size] w-[--control-size] min-h-[--control-size] min-w-[--control-size] [&>svg]:size-[calc(var(--control-size)/1.8)]; } } }