<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.
"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
/>
);Table Footer
Combine Pagination with a rows-per-page selector. Load the corresponding rows in your app.
"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
ariaLabel to name the pagination navigation landmark.previousLabel and nextLabel, or previousAriaLabel and nextAriaLabel for icon-only arrows.