Pagination

A pre composed pagination component

Preview

Installation

CLI

npx shadcn@latest add https://kelvinmai.io/r/pagination.json

Manual

Install the following dependencies

npm install clsx tailwind-merge

Add a classname utility function

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export const cn = (...inputs: ClassValue[]) => {
return twMerge(clsx(inputs));
};

Copy and paste the following code into your project

components/pagination.tsx
import * as React from 'react';
import {
ChevronLeft,
ChevronRight,
Ellipsis,
EllipsisVertical,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { cn } from '@/lib/utils';
import { DropdownMenuRadioGroup } from '@radix-ui/react-dropdown-menu';
type PaginationProps = React.ComponentProps<'nav'> & {
currentPage: number;
pages: number;
onPage: (page: number) => void;
buttonProps?: Omit<
React.ComponentProps<typeof Button>,
'disabled' | 'onClick'
>;
};
export const Pagination: React.FC<PaginationProps> = ({
className,
currentPage,
pages,
onPage,
buttonProps,
children,
...props
}) => {
return (
<nav
role='navigation'
aria-label='pagination'
className={cn('mx-auto flex w-full justify-center gap-1 my-4', className)}
{...props}
>
<Button
onClick={() => onPage(currentPage - 1)}
disabled={currentPage <= 1}
{...buttonProps}
>
<ChevronLeft />
<span>Previous</span>
</Button>
{currentPage >= 3 && (
<Button disabled {...buttonProps}>
<Ellipsis />
</Button>
)}
{currentPage >= 2 && (
<Button onClick={() => onPage(currentPage - 1)} {...buttonProps}>
{currentPage - 1}
</Button>
)}
<Button disabled {...buttonProps}>
{currentPage}
</Button>
{currentPage < pages - 1 && (
<Button onClick={() => onPage(currentPage + 1)} {...buttonProps}>
{currentPage + 1}
</Button>
)}
{pages - currentPage >= 2 && (
<Button disabled {...buttonProps}>
<Ellipsis />
</Button>
)}
{currentPage !== pages && (
<Button onClick={() => onPage(pages)} {...buttonProps}>
{pages}
</Button>
)}
<Button
onClick={() => onPage(currentPage + 1)}
disabled={currentPage >= pages}
{...buttonProps}
>
<span>Next</span>
<ChevronRight />
</Button>
{children}
</nav>
);
};
type PaginationPageSizeDropdownProps = React.ComponentProps<
typeof DropdownMenu
> & {
buttonProps?: React.ComponentProps<typeof Button>;
pageSizes?: number[];
total: number;
pageSize: number;
onPageSize: (pageSize: number) => void;
};
export const PaginationPageSizeDropdown: React.FC<
PaginationPageSizeDropdownProps
> = ({
buttonProps,
pageSizes = [5, 10, 25],
total,
pageSize,
onPageSize,
...props
}) => {
return (
<DropdownMenu {...props}>
<DropdownMenuTrigger asChild>
<Button {...buttonProps}>
<EllipsisVertical />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Total: {total}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuLabel>Select Page Size</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioGroup
value={String(pageSize)}
onValueChange={(v) => onPageSize(Number(v))}
>
{pageSizes.map((n) => (
<DropdownMenuRadioItem key={n} value={String(n)}>
{n}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
);
};

Update the import paths to match your project setup

Usage

import { Pagination } from '@/components/ui/pagination';
export default function PaginationDemo() {
const [pagination, setPagination] = React.useState({
currentPage: 1,
pages: 10,
});
const [limit, setLimit] = React.useState(10);
const buttonProps = { variant: 'ghost' };
return (
<Pagination
currentPage={pagination.currentPage}
pages={pagination.pages}
onPage={(page) => setPagination({ ...pagination, currentPage: page })}
buttonProps={buttonProps}
>
<PaginationPageSizeDropDown
total={1000}
pageSize={limit}
onPageSize={(pageSize) => setLimit(pageSize)}
buttonProps={buttonProps}
/>
</Pagination>
);
}

API Reference

Pagination props

NameTypeRequiredDescription
currentPage
number
Yes
The value of the current page.
pages
number
Yes
The value of total pages of the data set.
onPage
(page: number) => void
Yes
Function to call when page changes.
buttonProps
React.ComponentProps<typeof Button>
No
Props for each of the pagination buttons.

PaginationPageSizeDropDown props

NameTypeRequiredDescription
pageSize
number
Yes
The value of how many items to display per page
total
number
Yes
The total value of how many items are in the data set.
pageSizes
number[]
No
The page size options. The default options are [5, 10, 25]
onPageSize
(pageSize: number) => void
Yes
Function to update the item size of each page.
buttonProps
React.ComponentProps<typeof Button>
No
Props for each of the pagination buttons.