Skip to content
v2.5.0
English
Русский
Documentation

Pagination

Pagination is a compound component for paged navigation.
pagination.tsx · 11 lines
import { Pagination, PaginationItem, PaginationListItem } from '@vega-ui/react';

<nav aria-label='Pagination'>
  <Pagination>
    {[1, 2, 3, 4].map((page) => (
      <PaginationListItem key={page}>
        <PaginationItem current={page === 2}>{page}</PaginationItem>
      </PaginationListItem>
    ))}
  </Pagination>
</nav>
Browser support
Newly available 2024
  • Chrome 125
  • Edge 125
  • Firefox 128
  • Safari 18
  • Safari on iOS 18
Bundle size
Tested in
11 tests

Summary

Pagination is a compound component for paged navigation. It coordinates page items, trigger buttons, ellipsis placeholders, and optional supporting text while leaving the canonical page state in the parent feature.

Imports

import {
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationText,
  PaginationTriggerIconButton,
  type PaginationEllipsisProps,
  type PaginationItemProps,
  type PaginationListItemProps,
  type PaginationProps,
  type PaginationSize,
  type PaginationTextProps,
  type PaginationTriggerIconButtonProps,
  type PaginationVariant,
} from '@vega-ui/react';

Exported Types

  • PaginationProps
  • PaginationItemProps
  • PaginationTriggerIconButtonProps
  • PaginationEllipsisProps
  • PaginationListItemProps
  • PaginationTextProps
  • PaginationSize
  • PaginationVariant

Minimal Composition

Pagination renders a plain <ul> and keeps no page state: the parent owns the current page, and a <nav> with its own aria-label gives the list a landmark. Wrap every item and trigger in PaginationListItem, but not PaginationText, which already renders its own <li>. Icon-only triggers need an icon child and an aria-label.

minimal-composition.tsx · 36 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
} from '@vega-ui/react';
import { useState } from 'react';

export default function MinimalComposition() {
  const [page, setPage] = useState(1);

Required Parts

  • Pagination: root context and list container
  • PaginationListItem: list wrapper for each visible item
  • one of PaginationItem, PaginationTriggerIconButton, PaginationEllipsis, or PaginationText

Optional Parts

  • PaginationText: compact textual summary such as Page 3 of 20
  • PaginationEllipsis: gap indicator for truncated page ranges

Composition Order

  1. Pagination
  2. repeated PaginationListItem
  3. inside each item, one pagination child part

Variants

  • Size: inherited from button/icon-button sizing
  • Variant: primary or secondary
  • Interaction model: link-based or button-based pagination

Edge Cases

  • First and last page behavior should be explicit.
  • The pagination UI should not become the source of truth for data state.
  • Ellipsis should preserve current-page context rather than hiding it entirely.

Common Mistakes

  • Forgetting disabled behavior at page boundaries.
  • Treating pagination visuals as the only state model.
  • Using pagination when infinite scroll or filtering is a better fit for the data.

Examples

Basic

Basic: page window with ellipses

The parent keeps the current page in state and computes which pages to show with getPageItems(page, total, siblings): always the first and last page, the current page with one sibling on each side, and an ellipsis wherever at least two pages are hidden. Previous and next are disabled at the boundaries.

button-based-pagination.tsx · 68 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
} from '@vega-ui/react';
import { useState } from 'react';

type PageItem = number | 'start-ellipsis' | 'end-ellipsis';

Every page is a real <a href='?page=N'> rendered through asChild, so pages can be opened in a new tab, shared and crawled. The current page is read from the URL after mount and marked with aria-current='page'; at the boundaries the previous or next link loses its href and gets aria-disabled.

link-based-pagination.tsx · 91 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
} from '@vega-ui/react';
import { useEffect, useState } from 'react';

type PageItem = number | 'start-ellipsis' | 'end-ellipsis';

Controlled/Stateful

Controlled/Stateful: paging through a table

The page number slices a list of 47 orders into pages of 10. The summary next to the controls (“Showing 11–20 of 47 orders”) is derived from the same state and announced politely when the page changes.

paginated-order-list.tsx · 88 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
  Table,
  TableBody,
  TableData,
  TableHead,
  TableHeading,

Form/Integration

Form/Integration: go to a page

A small form next to the pagination jumps straight to a page among 48. The number input has min, max and required, so the browser rejects out-of-range values before submit; the handler reads the value with FormData and resets the field.

page-jump-form.tsx · 91 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Button,
  Icon,
  Label,
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
  TextField,
  TextFieldInput,

Layout/Overlay

In a dense table footer the page numbers give way to a PaginationText range (“6–10 of 23”) and two icon triggers. PaginationText renders its own list item, so it sits directly inside Pagination.

dense-footer-controls.tsx · 75 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationListItem,
  PaginationText,
  PaginationTriggerIconButton,
  Table,
  TableBody,
  TableData,
  TableFoot,
  TableHead,

Error

Error: current page disappears after filtering

The requested page lives in state separately from the page that is actually shown. When a filter leaves fewer pages than requested, the example shows the last existing page and an error HelperText linked to the navigation; clearing the filter brings the requested page back and removes the message.

stale-page-hint-after-filter-change.tsx · 77 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  HelperText,
  Icon,
  Label,
  Pagination,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
  Text,
  TextField,
  TextFieldInput,

Disabled

Disabled: locked while a page loads

While the next page is loading, a native <fieldset disabled> disables every page button and trigger at once, so the user cannot queue several requests. The status line below says which page is loading.

disabled-while-loading.tsx · 66 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
  Spinner,
  Text,
} from '@vega-ui/react';
import { useEffect, useState } from 'react';

Edge

Edge: very large ranges

The same window algorithm with first and last triggers on 7, 20 and 1000 pages and 0–2 siblings. The number of items stays constant while you move, so the controls do not jump; with few pages every page is shown and no ellipsis appears.

truncated-large-range.tsx · 109 lines
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from '@vega-ui/icons';
import {
  Button,
  Icon,
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
  Text,
} from '@vega-ui/react';
import { useState } from 'react';

Edge: right-to-left layout and Arabic numerals

Inside dir='rtl' the flex row reverses on its own, but the arrow icons do not: “previous” points right and “next” points left. Page numbers and their labels are formatted with Intl.NumberFormat('ar-EG').

rtl-pagination.tsx · 70 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
} from '@vega-ui/react';
import { useState } from 'react';

type PageItem = number | 'start-ellipsis' | 'end-ellipsis';

Anatomy

Overview

Pagination is a compositional navigation system. The root shares size and variant context, while each list item wraps one visible pagination affordance.

Required Parts

Pagination

Required. Owns shared size and variant context and renders the outer list.

PaginationListItem

Required in normal composition. Preserves list semantics for each visible pagination element.

PaginationItem or PaginationTriggerIconButton

Normally required. These are the main interactive page affordances.

Optional Parts

PaginationEllipsis

Optional. Indicates truncated ranges.

PaginationText

Optional. Shows compact textual pagination status.

Composition Order

  1. Pagination
  2. repeated PaginationListItem
  3. one child part per list item

Valid Composition Patterns

button-based-pagination.tsx · 68 lines
import { ChevronLeft, ChevronRight } from '@vega-ui/icons';
import {
  Icon,
  Pagination,
  PaginationEllipsis,
  PaginationItem,
  PaginationListItem,
  PaginationTriggerIconButton,
} from '@vega-ui/react';
import { useState } from 'react';

type PageItem = number | 'start-ellipsis' | 'end-ellipsis';

Invalid Composition Patterns

Interactive pagination parts rendered without PaginationListItem

The visuals may still work, but list semantics are weakened.

Pagination component storing the authoritative page state by itself

Canonical page state belongs to the parent feature or router.

Ellipsis used as an interactive substitute for real pages

The shipped ellipsis is a visual gap indicator, not page navigation.