Skip to main content

Pagination

Page navigation with previous and next links, numbers and ellipses.

1'use client';
2
3import * as React from 'react';
4import { Pagination } from '@/components/ui/pagination';
5
6const TOTAL_PAGES = 8;
7
8function pageList(current: number): (number | 'ellipsis')[] {
9 const pages = new Set([1, TOTAL_PAGES, current - 1, current, current + 1]);
10 const sorted = [...pages]
11 .filter((page) => page >= 1 && page <= TOTAL_PAGES)
12 .sort((a, b) => a - b);
13
14 const result: (number | 'ellipsis')[] = [];
15 let previous = 0;
16 for (const page of sorted) {
17 if (page - previous > 1) result.push('ellipsis');
18 result.push(page);
19 previous = page;
20 }
21 return result;
22}
23
24export function Default() {
25 const [page, setPage] = React.useState(4);
26
27 return (
28 <Pagination>
29 <Pagination.Content className="flex-wrap justify-center">
30 <Pagination.Item>
31 <Pagination.Previous
32 disabled={page === 1}
33 onClick={() => setPage((current) => Math.max(1, current - 1))}
34 className="max-sm:w-9 max-sm:px-0 max-sm:[&>span]:sr-only"
35 />
36 </Pagination.Item>
37 {pageList(page).map((item, index) =>
38 item === 'ellipsis' ? (
39 <Pagination.Item key={`ellipsis-${index}`}>
40 <Pagination.Ellipsis />
41 </Pagination.Item>
42 ) : (
43 <Pagination.Item key={item}>
44 <Pagination.Link isActive={item === page} onClick={() => setPage(item)}>
45 {item}
46 </Pagination.Link>
47 </Pagination.Item>
48 ),
49 )}
50 <Pagination.Item>
51 <Pagination.Next
52 disabled={page === TOTAL_PAGES}
53 onClick={() => setPage((current) => Math.min(TOTAL_PAGES, current + 1))}
54 className="max-sm:w-9 max-sm:px-0 max-sm:[&>span]:sr-only"
55 />
56 </Pagination.Item>
57 </Pagination.Content>
58 </Pagination>
59 );
60}

Installation

pnpm dlx nachui add pagination

Anatomy

1import { Pagination } from '@/components/ui/pagination';
1<Pagination>
2 <Pagination.Content>
3 <Pagination.Item>
4 <Pagination.Previous href="#" />
5 </Pagination.Item>
6 <Pagination.Item>
7 <Pagination.Link href="#">1</Pagination.Link>
8 </Pagination.Item>
9 <Pagination.Item>
10 <Pagination.Link href="#" isActive>
11 2
12 </Pagination.Link>
13 </Pagination.Item>
14 <Pagination.Item>
15 <Pagination.Ellipsis />
16 </Pagination.Item>
17 <Pagination.Item>
18 <Pagination.Next href="#" />
19 </Pagination.Item>
20 </Pagination.Content>
21</Pagination>
Pagination.Link renders an anchor when you pass href and a button when you leave it out, so the same markup works for routed pages and for client state. With Next.js, wrap the link parts or swap the anchor for next/link in your copy of the component.

Composition

Use the following composition to build a Pagination:
Pagination
└── Pagination.Content
├── Pagination.Item
│ └── Pagination.Previous
├── Pagination.Item
│ └── Pagination.Link
├── Pagination.Item
│ └── Pagination.Ellipsis
└── Pagination.Item
└── Pagination.Next

Variants

Compact

Icon-only previous and next around a page indicator, for toolbars and tight spaces.
1'use client';
2
3import * as React from 'react';
4import { Pagination } from '@/components/ui/pagination';
5
6const TOTAL_PAGES = 12;
7
8export function Compact() {
9 const [page, setPage] = React.useState(3);
10
11 return (
12 <Pagination>
13 <Pagination.Content>
14 <Pagination.Item>
15 <Pagination.Previous
16 iconOnly
17 disabled={page === 1}
18 onClick={() => setPage((current) => Math.max(1, current - 1))}
19 />
20 </Pagination.Item>
21 <Pagination.Item>
22 <span className="text-muted-foreground px-2 text-sm tabular-nums">
23 Page {page} of {TOTAL_PAGES}
24 </span>
25 </Pagination.Item>
26 <Pagination.Item>
27 <Pagination.Next
28 iconOnly
29 disabled={page === TOTAL_PAGES}
30 onClick={() => setPage((current) => Math.min(TOTAL_PAGES, current + 1))}
31 />
32 </Pagination.Item>
33 </Pagination.Content>
34 </Pagination>
35 );
36}

API Reference

Pagination

PropTypeDefaultDescription
labelstring'Pagination'Accessible name for the nav
classNamestring-Additional CSS classes
PropTypeDefaultDescription
hrefstring-Renders an anchor; omit to render a button
isActivebooleanfalseMarks the current page with aria-current
disabledbooleanfalseDisables the control
size'default' | 'icon''icon'Square page number or padded text link
classNamestring-Additional CSS classes

Pagination.Previous / Pagination.Next

PropTypeDefaultDescription
iconOnlybooleanfalseHide the text label visually
childrenReactNode'Previous' / 'Next'Label text
Both also accept every Pagination.Link prop except size.

Pagination.Ellipsis

PropTypeDefaultDescription
labelstring'More pages'Screen reader text
classNamestring-Additional CSS classes
Found something to improve?

Notice a bug, typo, or missing detail on this page? Help us make the documentation better by opening a GitHub issue.

Create an Issue
Ctrl+I