Skip to main content

Pagination

Pagination is a navigation control for moving through pages of content, like browsing search results or reviewing invoices. It shows numbered pages or a compact page count with previous and next controls.

Import

import { Pagination } from '@vortexlabs/vortex';
import type { PaginationProps } from '@vortexlabs/vortex';

Other links

<Stack
  width="100%"
  attributes={{"style":{"overflowX":"auto"}}}
>
  <Pagination
    total={10}
    defaultPage={5}
    ariaLabel="Pagination playground"
    previousAriaLabel="Previous page"
    nextAriaLabel="Next page"
  />
</Stack>

Features

  • Offers numbered and compact layouts.
  • Supports controlled and uncontrolled page selection.
  • Provides an optional page input with validation.
  • Disables previous and next controls at the ends.

Numbered Pages

Set total to the number of pages. Page numbers start at 1; defaultPage sets the starting page, and siblingCount controls how many page numbers appear on each side of the current page (default: 1).

"use client";

import { Pagination } from "@vortexlabs/vortex";

export const PaginationNumberedExample = () => (
  <Pagination
    ariaLabel="Pagination: numbered"
    previousAriaLabel="Previous page"
    nextAriaLabel="Next page"
    total={10}
    defaultPage={5}
  />
);

Compact Layout

Use variant="compact" for a page count such as “Page 5 of 10” between the arrow buttons. Supply pageLabel and totalLabel to format the text; omit pageInput or set it to false to display the current page as text.

"use client";

import { Pagination } from "@vortexlabs/vortex";

export const PaginationCompactExample = () => (
  <Pagination
    ariaLabel="Pagination: compact text"
    previousAriaLabel="Previous page"
    nextAriaLabel="Next page"
    variant="compact"
    pageLabel="Page"
    totalLabel={({ total }) => `of ${total}`}
    pageInput={false}
    total={10}
    defaultPage={5}
  />
);

Page Input

In compact mode, pass a pageInput object so users can enter a page number and press Enter. Provide ariaLabel for the input and an errorMessage function for entries that are not whole numbers from 1 to total.

Invalid entries leave the selected page unchanged. When total is 1, the current page is shown as text instead of an input.

"use client";

import { Pagination } from "@vortexlabs/vortex";

export const PaginationInputExample = () => (
  <Pagination
    ariaLabel="Pagination: page input and validation"
    previousAriaLabel="Previous page"
    nextAriaLabel="Next page"
    variant="compact"
    pageLabel="Page"
    totalLabel={({ total }) => `of ${total}`}
    pageInput={{
      ariaLabel: "Page number",
      errorMessage: ({ total }) => `Enter a whole number from 1 to ${total}.`,
    }}
    total={2500}
    defaultPage={1000}
  />
);

Controlled Selection

Pass page and update it from onChange to control selection. Use defaultPage instead when Pagination should manage selection; it only sets the starting page.

Pagination manages the page controls. Use the selected page in your app to display the corresponding items or fetch them from your API.

Selected page: 3
"use client";

import { Button, Pagination, Stack, Text } from "@vortexlabs/vortex";
import { useState } from "react";

export const PaginationControlledExample = () => {
  const [page, setPage] = useState(3);

  return (
    <Stack gap={3}>
      <Text size="body-sm-desktop" attributes={{ "aria-live": "polite" }}>
        Selected page: {page}
      </Text>
      <Pagination
        ariaLabel="Pagination: controlled"
        previousAriaLabel="Previous page"
        nextAriaLabel="Next page"
        total={8}
        page={page}
        onChange={({ page }) => setPage(page)}
      />
      <Stack direction="row">
        <Button variant="secondary" size="sm" onClick={() => setPage(1)}>
          Reset to page 1
        </Button>
      </Stack>
    </Stack>
  );
};

Full Width

In numbered mode, fullWidth fills the container and places the previous and next arrows at its edges.

"use client";

import { Pagination } from "@vortexlabs/vortex";

export const PaginationWidthExample = () => (
  <Pagination
    ariaLabel="Pagination: full width"
    previousAriaLabel="Previous page"
    nextAriaLabel="Next page"
    total={10}
    defaultPage={5}
    fullWidth
  />
);

Combine Pagination with a rows-per-page selector. Load the corresponding rows in your app.

101–125 of 2,500 rows
"use client";

import { Pagination, Select, Stack, Text } from "@vortexlabs/vortex";
import { useState } from "react";

export const PaginationFooterExample = () => {
  const [page, setPage] = useState(5);
  const [rowsPerPage, setRowsPerPage] = useState(25);
  const totalRows = 2500;
  const firstRow = (page - 1) * rowsPerPage + 1;
  const lastRow = Math.min(page * rowsPerPage, totalRows);

  return (
    <Stack
      direction="row"
      align="start"
      justify="space-between"
      gap={4}
      padding={4}
      borderColor="default"
      attributes={{ style: { borderColor: "var(--vortex-color-border-divider)" } }}
      wrap
    >
      <Stack minHeight={10} justify="center">
        <Text size="body-sm-desktop" tabularNums>
          {firstRow}–{lastRow} of {totalRows.toLocaleString("en")} rows
        </Text>
      </Stack>
      <Stack direction="row" align="start" gap={5} wrap>
        <Select.Root width="auto">
          <Stack direction="row" align="center" gap={2}>
            <Select.Label marginBottom={0} wrap="nowrap">
              <Text as="span" size="body-sm-desktop" weight="regular">
                Rows per page
              </Text>
            </Select.Label>
            <Stack width="72px">
              <Select.Field value={String(rowsPerPage)}>
                {[10, 25, 50].map((size) => (
                  <Select.Item
                    key={size}
                    selected={size === rowsPerPage}
                    onClick={() => {
                      setRowsPerPage(size);
                      setPage(1);
                    }}
                  >
                    {size}
                  </Select.Item>
                ))}
              </Select.Field>
            </Stack>
          </Stack>
        </Select.Root>
        <Pagination
          ariaLabel="Pagination: large dataset"
          previousAriaLabel="Previous page"
          nextAriaLabel="Next page"
          variant="compact"
          pageLabel="Page"
          totalLabel={({ total }) => `of ${total}`}
          pageInput={{
            ariaLabel: "Page number",
            errorMessage: ({ total }) => `Enter a whole number from 1 to ${total}.`,
          }}
          total={Math.ceil(totalRows / rowsPerPage)}
          page={page}
          onChange={({ page }) => setPage(page)}
        />
      </Stack>
    </Stack>
  );
};

Accessibility

Key
Description
TabShiftTab
Move between enabled controls and the optional page input.
EnterSpace
Activate a focused page, previous, or next button.
Enter
Apply the page input's value without submitting a surrounding form.
Provide ariaLabel to name the pagination navigation landmark.
Provide previousLabel and nextLabel, or previousAriaLabel and nextAriaLabel for icon-only arrows.