Pagination
Migration guide for Pagination from HeroUI v2 to v3
Refer to the v3 Pagination documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
Structure Changes
In v2, Pagination was a single component that handled all rendering internally via props:
import { Pagination } from "@heroui/react";
export default function App() {
return (
<Pagination total={10} initialPage={1} showControls />
);
}
In v3, Pagination uses a compound component pattern where you compose each part explicitly:
import { Pagination } from "@heroui/react";
export default function App() {
return (
<Pagination>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous>
<Pagination.PreviousIcon />
</Pagination.Previous>
</Pagination.Item>
<Pagination.Item>
<Pagination.Link isActive>1</Pagination.Link>
</Pagination.Item>
<Pagination.Item>
<Pagination.Link>2</Pagination.Link>
</Pagination.Item>
<Pagination.Item>
<Pagination.Link>3</Pagination.Link>
</Pagination.Item>
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
<Pagination.Item>
<Pagination.Link>10</Pagination.Link>
</Pagination.Item>
<Pagination.Item>
<Pagination.Next>
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
);
}
Key Changes
1. Component Structure
v2: Single Pagination component that auto-generates page items from total prop
v3: Compound components: Pagination, Pagination.Summary, Pagination.Content, Pagination.Item, Pagination.Link, Pagination.Previous, Pagination.PreviousIcon, Pagination.Next, Pagination.NextIcon, Pagination.Ellipsis
2. Page Generation
v2: Pages auto-generated from total, siblings, boundaries props
v3: You compose page items manually, giving full control over layout and behavior. Build your own pagination logic or use a pagination hook.
3. Prop Changes
| v2 Prop | v3 Equivalent | Notes |
|---|---|---|
total | - | Removed (compose items manually) |
page | - | Manage active state via isActive on Pagination.Link |
initialPage | - | Manage state externally |
onChange | - | Use onPress on individual Pagination.Link components |
siblings | - | Removed (compose items manually) |
boundaries | - | Removed (compose items manually) |
dotsJump | - | Removed (handle ellipsis click manually) |
loop | - | Removed (implement manually) |
showControls | - | Compose Pagination.Previous and Pagination.Next |
isCompact | - | Removed (style with Tailwind CSS) |
showShadow | - | Removed (use Tailwind shadow-* classes) |
size | Pagination size | Same (sm, md, lg) |
variant | - | Removed (use Tailwind CSS) |
color | - | Removed (use Tailwind CSS) |
radius | - | Removed (use Tailwind CSS) |
isDisabled | isDisabled on Pagination.Link, Pagination.Previous, Pagination.Next | Per-item instead of global |
disableCursorAnimation | - | Removed |
disableAnimation | - | Removed |
renderItem | - | Compose items directly |
getItemAriaLabel | - | Set aria-label on individual items |
classNames | - | Use className on individual compound components |
4. Hook Changes
v2: usePagination hook available for custom implementations
v3: No built-in hook — compose pagination items directly or build your own pagination logic
Migration Examples
Basic Pagination
v2
```tsx
import { Pagination } from "@heroui/react";
<Pagination total={10} initialPage={1} />
```
v3
```tsx
import { useState } from "react";
import { Pagination } from "@heroui/react";
const [page, setPage] = useState(1);
const totalPages = 10;
<Pagination>
<Pagination.Content>
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
<Pagination.Item key={p}>
<Pagination.Link isActive={page === p} onPress={() => setPage(p)}>
{p}
</Pagination.Link>
</Pagination.Item>
))}
</Pagination.Content>
</Pagination>
```
With Previous / Next Controls
v2
```tsx
<Pagination total={10} initialPage={1} showControls />
```
v3
```tsx
const [page, setPage] = useState(1);
const totalPages = 10;
<Pagination>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
isDisabled={page === 1}
onPress={() => setPage((p) => Math.max(1, p - 1))}
>
<Pagination.PreviousIcon />
</Pagination.Previous>
</Pagination.Item>
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
<Pagination.Item key={p}>
<Pagination.Link isActive={page === p} onPress={() => setPage(p)}>
{p}
</Pagination.Link>
</Pagination.Item>
))}
<Pagination.Item>
<Pagination.Next
isDisabled={page === totalPages}
onPress={() => setPage((p) => Math.min(totalPages, p + 1))}
>
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
```
With Ellipsis
v2
```tsx
<Pagination total={20} initialPage={1} siblings={1} boundaries={1} />
```
v3
```tsx
const [page, setPage] = useState(1);
const totalPages = 20;
<Pagination>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
isDisabled={page === 1}
onPress={() => setPage((p) => Math.max(1, p - 1))}
>
<Pagination.PreviousIcon />
</Pagination.Previous>
</Pagination.Item>
<Pagination.Item>
<Pagination.Link isActive={page === 1} onPress={() => setPage(1)}>
1
</Pagination.Link>
</Pagination.Item>
{page > 3 && (
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
)}
{[page - 1, page, page + 1]
.filter((p) => p > 1 && p < totalPages)
.map((p) => (
<Pagination.Item key={p}>
<Pagination.Link isActive={page === p} onPress={() => setPage(p)}>
{p}
</Pagination.Link>
</Pagination.Item>
))}
{page < totalPages - 2 && (
<Pagination.Item>
<Pagination.Ellipsis />
</Pagination.Item>
)}
<Pagination.Item>
<Pagination.Link
isActive={page === totalPages}
onPress={() => setPage(totalPages)}
>
{totalPages}
</Pagination.Link>
</Pagination.Item>
<Pagination.Item>
<Pagination.Next
isDisabled={page === totalPages}
onPress={() => setPage((p) => Math.min(totalPages, p + 1))}
>
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
```
Simple Previous / Next Only
v2
```tsx
{/* v2 didn't have a built-in simple prev/next pattern */}
```
v3
```tsx
const [page, setPage] = useState(1);
const totalPages = 10;
<Pagination>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous
isDisabled={page === 1}
onPress={() => setPage((p) => p - 1)}
>
<Pagination.PreviousIcon />
<span>Previous</span>
</Pagination.Previous>
</Pagination.Item>
<Pagination.Item>
<Pagination.Next
isDisabled={page === totalPages}
onPress={() => setPage((p) => p + 1)}
>
<span>Next</span>
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
```
With Summary
v2
```tsx
{/* v2 didn't have a built-in summary slot */}
```
v3
```tsx
const [page, setPage] = useState(1);
const perPage = 10;
const total = 100;
<Pagination>
<Pagination.Summary>
Showing {(page - 1) * perPage + 1}-{Math.min(page * perPage, total)} of {total}
</Pagination.Summary>
<Pagination.Content>
{/* Pagination items */}
</Pagination.Content>
</Pagination>
```
Custom Icons
v2
```tsx
{/* v2 required renderItem for custom icons */}
```
v3
```tsx
import { Icon } from "@iconify/react";
<Pagination>
<Pagination.Content>
<Pagination.Item>
<Pagination.Previous onPress={() => setPage((p) => p - 1)}>
<Pagination.PreviousIcon>
<Icon icon="gravity-ui:arrow-left" />
</Pagination.PreviousIcon>
</Pagination.Previous>
</Pagination.Item>
{/* Page links */}
<Pagination.Item>
<Pagination.Next onPress={() => setPage((p) => p + 1)}>
<Pagination.NextIcon>
<Icon icon="gravity-ui:arrow-right" />
</Pagination.NextIcon>
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
```
Styling Changes
v2: classNames Prop
<Pagination
classNames={{
base: "custom-base",
wrapper: "custom-wrapper",
prev: "custom-prev",
next: "custom-next",
item: "custom-item",
cursor: "custom-cursor",
ellipsis: "custom-ellipsis",
}}
/>
v3: Direct className Props
<Pagination className="custom-base">
<Pagination.Content className="custom-wrapper">
<Pagination.Item className="custom-item">
<Pagination.Previous className="custom-prev">
<Pagination.PreviousIcon />
</Pagination.Previous>
</Pagination.Item>
<Pagination.Item>
<Pagination.Link isActive className="custom-cursor">1</Pagination.Link>
</Pagination.Item>
<Pagination.Item>
<Pagination.Ellipsis className="custom-ellipsis" />
</Pagination.Item>
<Pagination.Item>
<Pagination.Next className="custom-next">
<Pagination.NextIcon />
</Pagination.Next>
</Pagination.Item>
</Pagination.Content>
</Pagination>
Component Anatomy
The v3 Pagination follows this structure:
Pagination (Root, nav element)
├── Pagination.Summary (optional, info text)
└── Pagination.Content (ul container)
└── Pagination.Item (li wrapper, repeated)
├── Pagination.Previous (with Pagination.PreviousIcon)
├── Pagination.Link (page number, isActive for current)
├── Pagination.Ellipsis
└── Pagination.Next (with Pagination.NextIcon)
Summary
- Component Structure: Single auto-generating component → compound components with manual composition
- Page Generation:
total/siblings/boundariesprops → compose page items manually with your own pagination logic - Active Page:
page/initialPageprops →isActiveprop on individualPagination.Link - Navigation Controls:
showControlsprop → composePagination.PreviousandPagination.Nextdirectly - Ellipsis: Auto-generated → compose
Pagination.Ellipsismanually where needed - Events: Single
onChange→ individualonPresshandlers on eachPagination.Link - New Features:
Pagination.Summaryfor showing result counts, custom icons viaPreviousIcon/NextIconchildren - Hook Removed:
usePagination→ build your own pagination logic - Styling Props Removed:
variant,color,radius,isCompact,showShadow→ use Tailwind CSS - ClassNames Removed: Use
classNameon individual compound components
Local source: vendor/en/react/migration/(components)/pagination.mdx
Generated from the pinned HeroUI v3 commit with internal links and previews adapted for JGuang.