XDSPagination@xds/core · Pagination
Usage
Pagination lets users step through pages of content. Place it below a table, list, or card grid so users can move forward and backward through results. Pick a variant to match the context — numbered pages for data tables, a count for large lists, compact for tight spaces, or dots for carousels.Best practices
| Guidance | Practices |
|---|---|
| Do | Place pagination below the content it controls so users see results before navigating. |
| Do | Use the pages variant for data tables where users need to jump to a specific page. |
| Do | Use the count variant with a page size selector when users need to control how many items they see at once. |
| Do | Use the dots variant for carousels and walkthroughs where the total is small and position matters more than a number. |
| Do | Pass totalItems when the total is known so users can see how much content remains. |
| Don't | Show pagination when all items fit on a single page — there is nothing to paginate. |
| Don't | Use the dots variant for more than about 10 pages — the dots become too small to be useful. |
| Don't | Place pagination above the content — users expect it at the bottom. |
Import
tsimport {XDSPagination} from '@xds/core/Pagination'
Props
| Prop | Type | Description |
|---|---|---|
pagerequired | number | Current page number (1-based). Page 1 is the first page. |
onChangerequired | (page: number) => void | Called when the page changes. |
changeAction | (page: number) => void | Promise<void> | Async action on page change. Fires after onChange and uses React transitions for built-in loading state. |
totalItems | number | Total number of items. Used to calculate page count. Takes precedence over totalPages if both provided. |
totalPages | number | Total number of pages. Use when you know page count but not item count. |
hasMore | boolean | Whether more pages exist after the current one. Use for cursor-based pagination where total is unknown. |
pageSize | number (default: 10) | Number of items per page. |
pageSizeOptions | number[] | Available page size options. Shows a page size selector dropdown when provided. |
onPageSizeChange | (pageSize: number) => void | Called when the page size changes. Automatically resets to page 1. |
variant | 'pages' | 'count' | 'compact' | 'dots' | 'none' (default: 'pages') | Visual variant controlling what appears between prev/next buttons. 'pages' shows page number buttons with ellipsis, 'count' shows 'X-Y of Z' text, 'compact' shows 'Page X of Y', 'dots' shows dot indicators, 'none' shows just prev/next buttons. |
siblingCount | number (default: 1) | Number of page buttons to show on each side of the current page. Only applies when variant='pages'. |
size | 'sm' | 'md' (default: 'md') | Size of the pagination controls. |
isDisabled | boolean (default: false) | Whether the component is disabled. |
label | string (default: 'Pagination') | Accessible label for the navigation landmark. |
xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}. |
Examples
Common configurations, variations, and states.Pagination — Dots Carousel
tsx'use client';import {useState} from 'react';import {XDSPagination} from '@xds/core/Pagination';import {XDSCard} from '@xds/core/Card';import {XDSText} from '@xds/core/Text';import {XDSStack} from '@xds/core/Layout';import {XDSAvatar} from '@xds/core/Avatar';import {XDSIcon} from '@xds/core/Icon';import {StarIcon} from '@heroicons/react/24/solid';import * as stylex from '@stylexjs/stylex';const REVIEWS = [{name: 'Jeannie Grant',date: 'June 01, 2025',stars: 5,quote:'A thorough report was done on our financial situation. Better deals were found and processed on our behalf, which took a lot of stress away.',},{name: 'Derval Russell',date: 'November 09, 2025',stars: 5,quote:'I have been a client for 8 years now and have always found the advice provided excellent. They take the time to explain things clearly.',},{name: 'Claire Dawson',date: 'October 15, 2025',stars: 5,quote:'Constantly professional and concise. Our mortgage process was smooth from start to finish thanks to their dedicated team.',},{name: 'Marcus Webb',date: 'September 22, 2025',stars: 4,quote:'Great service overall. The team was responsive and knowledgeable. Would definitely recommend to anyone looking for solid financial advice.',},];const styles = stylex.create({root: {maxWidth: 480,width: '100%',},pagination: {justifyContent: 'center',paddingTop: 4,},});function Stars({count}: {count: number}) {return (<XDSStack direction="horizontal" gap={0}>{Array.from({length: count}, (_, i) => (<XDSIcon key={i} icon={StarIcon} size="sm" color="warning" />))}</XDSStack>);}export default function PaginationDotsCarousel() {const [page, setPage] = useState(1);const review = REVIEWS[page - 1];return (<XDSStack direction="vertical" gap={3} xstyle={styles.root}><XDSCard padding={5}><XDSStack direction="vertical" gap={3}><Stars count={review.stars} /><XDSText type="body">{review.quote}</XDSText><XDSStackdirection="horizontal"gap={3}vAlign="center"hAlign="start"><XDSAvatar name={review.name} size="small" /><XDSStack direction="vertical" gap={0}><XDSText type="supporting" weight="bold">{review.name}</XDSText><XDSText type="supporting" color="secondary">{review.date}</XDSText></XDSStack></XDSStack></XDSStack></XDSCard><XDSPaginationpage={page}onChange={setPage}totalPages={REVIEWS.length}variant="dots"xstyle={styles.pagination}/></XDSStack>);}
Pagination — Page Size SelectorA transactions table with pagination and a page size dropdown at the bottom. Shows how pagination works as a footer below real content, with adjustable rows per page.
tsx'use client';import {useState} from 'react';import {XDSPagination} from '@xds/core/Pagination';import {XDSHeading} from '@xds/core/Text';import {XDSStack} from '@xds/core/Layout';import {XDSTable} from '@xds/core/Table';import * as stylex from '@stylexjs/stylex';const styles = stylex.create({root: {width: '100%',},pagination: {paddingTop: 8,flexDirection: 'row-reverse',},});const DATA = [{id: '1',date: 'Apr 18',description: 'Payment received',amount: '$2,450.00',},{id: '2',date: 'Apr 15',description: 'Subscription renewal',amount: '$99.00',},{id: '3', date: 'Apr 12', description: 'Refund issued', amount: '-$180.00'},{id: '4', date: 'Apr 10', description: 'Invoice paid', amount: '$1,200.00'},];export default function PaginationPageSize() {const [page, setPage] = useState(1);const [pageSize, setPageSize] = useState(10);return (<XDSStack direction="vertical" xstyle={styles.root}><XDSHeading level={4}>Transactions</XDSHeading><XDSTableidKey="id"columns={[{key: 'date', header: 'Date'},{key: 'description', header: 'Description'},{key: 'amount', header: 'Amount'},]}data={DATA}/><XDSPaginationpage={page}onChange={setPage}totalItems={350}pageSize={pageSize}onPageSizeChange={setPageSize}pageSizeOptions={[10, 25, 50, 100]}variant="count"xstyle={styles.pagination}/></XDSStack>);}
Pagination — With TablePagination below a data table with client-side page slicing. Use the count variant with small size for dense data views where users need to see item ranges.
tsx'use client';import {useState} from 'react';import {XDSPagination} from '@xds/core/Pagination';import {XDSTable} from '@xds/core/Table';import {XDSStack} from '@xds/core/Layout';import * as stylex from '@stylexjs/stylex';const styles = stylex.create({root: {width: '100%',},pagination: {justifyContent: 'center',paddingTop: 8,},});const ALL_DATA = [{id: '1', name: 'Olivia Chen', role: 'Engineer', status: 'Active'},{id: '2', name: 'Marcus Rivera', role: 'Designer', status: 'Active'},{id: '3', name: 'Aisha Patel', role: 'Marketing', status: 'Invited'},{id: '4', name: 'James Okafor', role: 'Engineer', status: 'Active'},{id: '5', name: 'Sofia Nguyen', role: 'Sales', status: 'Active'},{id: '6', name: 'Liam Johansson', role: 'Engineer', status: 'Inactive'},{id: '7', name: 'Elena Kowalski', role: 'Designer', status: 'Active'},{id: '8', name: 'David Kim', role: 'Marketing', status: 'Active'},{id: '9', name: 'Priya Sharma', role: 'Sales', status: 'Invited'},{id: '10', name: 'Noah Tanaka', role: 'Engineer', status: 'Active'},{id: '11', name: 'Fatima Al-Rashid', role: 'Designer', status: 'Active'},{id: '12', name: 'Carlos Mendez', role: 'Marketing', status: 'Inactive'},];const PAGE_SIZE = 4;export default function PaginationWithTable() {const [page, setPage] = useState(1);const start = (page - 1) * PAGE_SIZE;const pageData = ALL_DATA.slice(start, start + PAGE_SIZE);return (<XDSStack direction="vertical" xstyle={styles.root}><XDSTableidKey="id"columns={[{key: 'name', header: 'Name'},{key: 'role', header: 'Role'},{key: 'status', header: 'Status'},]}data={pageData}/><XDSPaginationpage={page}onChange={setPage}totalItems={ALL_DATA.length}pageSize={PAGE_SIZE}variant="count"size="sm"xstyle={styles.pagination}/></XDSStack>);}
Showcase source
tsx'use client';import {useState} from 'react';import {XDSPagination} from '@xds/core/Pagination';import {XDSStack} from '@xds/core/Layout';import * as stylex from '@stylexjs/stylex';const styles = stylex.create({root: {width: '100%',},});export default function PaginationVariants() {const [pagesPage, setPagesPage] = useState(3);const [countPage, setCountPage] = useState(2);const [compactPage, setCompactPage] = useState(5);const [dotsPage, setDotsPage] = useState(3);return (<XDSStack direction="vertical" gap={5} hAlign="center" xstyle={styles.root}><XDSPaginationpage={dotsPage}onChange={setDotsPage}totalPages={8}variant="dots"/><XDSPaginationpage={compactPage}onChange={setCompactPage}totalPages={10}variant="compact"/><XDSPaginationpage={countPage}onChange={setCountPage}totalItems={200}pageSize={20}variant="count"/><XDSPaginationpage={pagesPage}onChange={setPagesPage}totalItems={200}pageSize={10}variant="pages"/></XDSStack>);}