Installation
pnpm dlx shadcn@latest add https://rdf.dev.360online.com/r/pagination.jsonInstalls to @/components/ui/pagination.
Manual installation
Install the dependencies:
pnpm add lucide-react@^1.48.0Add the components it builds on: button.
Copy the source into your project:
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon } from 'lucide-react'
import type * as React from 'react'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
className={cn('mx-auto flex w-full justify-center', className)}
{...props}
/>
)
}
function PaginationContent({ className, ...props }: React.ComponentProps<'ul'>) {
return (
<ul
data-slot="pagination-content"
className={cn('group/pagination flex items-center gap-0.5', className)}
{...props}
/>
)
}
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} />
}
type PaginationLinkProps = {
isActive?: boolean
/** Total page count. When set, small screens show only the active link as "current / total" and hide the other page links and ellipses. */
total?: number
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<'a'>
function PaginationLink({ className, isActive, size = 'icon', total, children, ...props }: PaginationLinkProps) {
const compact = total !== undefined
const showTotal = isActive && compact
return (
<Button
variant={isActive ? 'outline' : 'ghost'}
size={size}
className={cn(compact && !isActive && 'max-sm:hidden', showTotal && 'max-sm:w-auto max-sm:px-3', className)}
nativeButton={false}
render={
<a
aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link"
data-active={isActive}
data-compact={compact || undefined}
{...props}
>
{children}
{showTotal && <span className="ms-1 sm:hidden">/ {total}</span>}
</a>
}
/>
)
}
function PaginationPrevious({
className,
text = 'Previous',
...props
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
<PaginationLink aria-label="Go to previous page" size="default" className={cn('pl-1.5!', className)} {...props}>
<ChevronLeftIcon data-icon="inline-start" />
<span className="hidden sm:block">{text}</span>
</PaginationLink>
)
}
function PaginationNext({
className,
text = 'Next',
...props
}: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
return (
<PaginationLink aria-label="Go to next page" size="default" className={cn('pr-1.5!', className)} {...props}>
<span className="hidden sm:block">{text}</span>
<ChevronRightIcon data-icon="inline-end" />
</PaginationLink>
)
}
function PaginationEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn(
"flex size-10 items-center justify-center max-sm:group-has-data-compact/pagination:hidden [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<MoreHorizontalIcon />
<span className="sr-only">More pages</span>
</span>
)
}
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
}A pagination component that helps users navigate through multiple pages of content. Built with accessibility in mind, it provides a consistent way to handle page navigation in lists, tables, or any content that spans multiple pages.
Features
- Accessible
<nav>landmark witharia-label="pagination" - Page links rendered as anchors (
<a>) styled withButton, so they work withhrefand client routers - Active page highlighting (
isActiverenders the outline button variant and setsaria-current="page") - Previous/Next links with a chevron icon; the label is set with the
textprop and hidden below thesmbreakpoint - Ellipsis for long page ranges
- Button icon sizes for page links (
icon-xs,icon-sm,icon,icon-lg) - Centered by default (
justify-center); override withclassName
Usage
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from '@/components/ui/pagination'
export function BasicPagination() {
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious href="#" />
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">1</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#" isActive>
2
</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationLink href="#">3</PaginationLink>
</PaginationItem>
<PaginationItem>
<PaginationEllipsis />
</PaginationItem>
<PaginationItem>
<PaginationNext href="#" />
</PaginationItem>
</PaginationContent>
</Pagination>
)
}Controlled
PaginationLink renders an anchor, so prevent the default navigation when handling clicks in state.
There is no disabled prop; mark an unavailable control with aria-disabled and disable pointer events.
export function ControlledPagination() {
const [page, setPage] = React.useState(1)
const isFirst = page === 1
return (
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
aria-disabled={isFirst}
tabIndex={isFirst ? -1 : undefined}
className={isFirst ? 'pointer-events-none opacity-50' : undefined}
onClick={(e) => {
e.preventDefault()
setPage((p) => Math.max(1, p - 1))
}}
/>
</PaginationItem>
{/* Page numbers */}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault()
setPage((p) => p + 1)
}}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
)
}Custom Labels
<PaginationPrevious href="#" text="Earlier meeting" />
<PaginationNext href="#" text="Later meeting" />Examples
Default
Basic pagination with a few pages and navigation controls.
With More Pages
Pagination with ellipsis for handling many pages.
Custom Text
Previous and Next labels set with the text prop, here stepping between council meetings.
Sizes
Page links accept the Button size prop. They default to icon.
With Disabled Controls
Pagination with a disabled Previous control.
Controlled
A public diary paged in state, with a sliding window of page numbers and ellipses.
Table Footer
A compact footer for a case list: a result summary, rows per page and icon-only first/previous/next/last links with aria-labels.
Subcomponents
Pagination
The root container component that wraps the pagination controls.
PaginationContent
A container for pagination items, providing proper layout and spacing.
PaginationItem
A wrapper for individual pagination elements.
PaginationLink
An anchor for page numbers, styled as a Button. Accepts all a props plus:
isActive- Marks the current page (outline variant,aria-current="page")size-Buttonsize, defaults toicon
PaginationPrevious
A link for navigating to the previous page. Accepts PaginationLink props plus text (default "Previous").
PaginationNext
A link for navigating to the next page. Accepts PaginationLink props plus text (default "Next").
PaginationEllipsis
An indicator for skipped page numbers.
Accessibility
The Pagination component follows WAI-ARIA guidelines:
- Uses semantic HTML elements
- Provides proper ARIA labels
- Supports keyboard navigation
- Indicates current page
- Communicates disabled states
- Maintains focus management
- Screen reader friendly
Best Practices
-
Navigation:
- Keep consistent page ordering
- Provide clear visual feedback
- Handle edge cases (first/last page)
- Consider mobile interactions
-
Layout:
- Maintain proper spacing
- Ensure touch targets are large enough
- Use clear visual hierarchy
- Consider responsive behavior
-
Interaction:
- Handle loading states
- Provide immediate feedback
- Maintain URL synchronization
- Consider infinite scroll alternatives
-
Accessibility:
- Use descriptive labels
- Support keyboard shortcuts
- Maintain focus states
- Consider screen readers
-
Implementation:
- Handle state changes gracefully
- Consider performance implications
- Implement proper error handling
- Maintain URL structure
Related Components
- Table
- Data Table
- Button