Pagination
Navigation for moving between pages of content.
npx shadcn@latest add @velaui/paginationpnpm dlx velaui add @velaui/paginationyarn dlx shadcn@latest add @velaui/paginationbunx --bun shadcn@latest add @velaui/paginationveni add paginationInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/paginationpnpm dlx velaui add @velaui/paginationyarn dlx shadcn@latest add @velaui/paginationbunx --bun shadcn@latest add @velaui/paginationExamples
- Default
- High emphasis
- With ellipsis
- Disabled boundary
Usage
Props
Pagination
| Prop | Type | Default |
|---|---|---|
emphasis | PaginationEmphasis | "medium" |
Takes these and every prop of PaginationProps.
PaginationContent
Takes every prop of PaginationContentProps.
PaginationEllipsis
Takes every prop of PaginationEllipsisProps.
PaginationItem
Takes every prop of PaginationItemProps.
PaginationLink
| Prop | Type | Default |
|---|---|---|
isActive | boolean | — |
Takes these and every prop of PaginationLinkProps.
PaginationNext
| Prop | Type | Default |
|---|---|---|
size | "default" | "icon" | "default" |
text | string | "Next" |
Takes these and every prop of PaginationNavProps.
PaginationPrevious
| Prop | Type | Default |
|---|---|---|
size | "default" | "icon" | "default" |
text | string | "Prev" |
Takes these and every prop of PaginationNavProps.
Dependencies
- npm
class-variance-authority@0.7.1- Installs with it
- 2 icons
Source
components/ui/pagination.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronLeft } from "@/components/icons/chevron-left";
import { ChevronRight } from "@/components/icons/chevron-right";
import { cn } from "@/lib/utils";
type PaginationEmphasis = "medium" | "high";
export type PaginationProps = React.ComponentProps<"nav"> & { emphasis?: PaginationEmphasis };
function Pagination({
className,
emphasis = "medium",
...props
}: PaginationProps) {
return (
<nav
role="navigation"
aria-label="pagination"
data-slot="pagination"
// Emphasis is a whole-pagination property in the design — it only ever
// repaints the selected page — so it cascades from here.
data-emphasis={emphasis}
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
);
}
export type PaginationContentProps = React.ComponentProps<"ul">;
function PaginationContent({
className,
...props
}: PaginationContentProps) {
return (
<ul
data-slot="pagination-content"
// 4px between page numbers. The nav buttons and the ellipsis each add
// 12px of their own margin to reach the design's 16px at those seams.
className={cn("flex h-10 items-center gap-1", className)}
{...props}
/>
);
}
export type PaginationItemProps = React.ComponentProps<"li">;
function PaginationItem({ ...props }: PaginationItemProps) {
return <li data-slot="pagination-item" {...props} />;
}
const paginationLinkVariants = cva(
[
"relative inline-flex size-10 shrink-0 cursor-pointer items-center justify-center",
"rounded-full text-sm leading-5 font-medium tracking-[-0.2px] text-text-strong",
"transition-colors outline-none",
"focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow",
"aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:text-text-disabled",
// Hover — 5% surface, and the circle squares off to a 10px radius.
"hover:rounded-[10px] hover:bg-bg-surface-1",
// Selected, medium emphasis — 10% surface, semibold. Hover is pinned back
// so the unqualified hover fill can't lighten a selected page.
"data-[active=true]:rounded-[10px] data-[active=true]:bg-bg-surface-2 data-[active=true]:font-semibold",
"data-[active=true]:hover:bg-bg-surface-2",
// Selected, high emphasis — solid foreground, inverse label. Same pin.
"in-data-[emphasis=high]:data-[active=true]:bg-bg-foreground in-data-[emphasis=high]:data-[active=true]:text-text-inverse",
"in-data-[emphasis=high]:data-[active=true]:hover:bg-bg-foreground",
].join(" "),
);
export type PaginationLinkProps = {
isActive?: boolean;
} & React.ComponentProps<"a">;
function PaginationLink({ className, isActive, ...props }: PaginationLinkProps) {
return (
<a
aria-current={isActive ? "page" : undefined}
data-slot="pagination-link"
data-active={isActive}
className={cn(paginationLinkVariants(), className)}
{...props}
/>
);
}
const paginationNavVariants = cva(
[
"inline-flex shrink-0 cursor-pointer items-center justify-center",
"text-sm leading-[1.4] font-semibold text-text-strong",
"transition-colors outline-none",
"focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow",
"aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:text-text-disabled",
"[&_svg]:size-5 [&_svg]:shrink-0",
].join(" "),
{
variants: {
size: {
// The design draws the labelled nav as a pill with 16px padding; the
// icon-only nav squares off at 8px like the page numbers.
default: "h-10 gap-2 rounded-full px-4",
icon: "size-10 rounded-[8px]",
},
},
defaultVariants: {
size: "default",
},
},
);
export type PaginationNavProps = React.ComponentProps<"a"> &
VariantProps<typeof paginationNavVariants> & { text?: string };
function PaginationPrevious({
className,
size = "default",
text = "Prev",
children,
...props
}: PaginationNavProps) {
return (
<a
aria-label="Go to previous page"
data-slot="pagination-previous"
// 12px on top of the list's 4px gap → the design's 16px seam.
className={cn(paginationNavVariants({ size }), "mr-3", className)}
{...props}
>
{children ?? (
<>
<ChevronLeft data-icon="inline-start" />
{size !== "icon" && <span className="hidden sm:block">{text}</span>}
</>
)}
</a>
);
}
function PaginationNext({
className,
size = "default",
text = "Next",
children,
...props
}: PaginationNavProps) {
return (
<a
aria-label="Go to next page"
data-slot="pagination-next"
className={cn(paginationNavVariants({ size }), "ml-3", className)}
{...props}
>
{children ?? (
<>
{size !== "icon" && <span className="hidden sm:block">{text}</span>}
<ChevronRight data-icon="inline-end" />
</>
)}
</a>
);
}
export type PaginationEllipsisProps = React.ComponentProps<"span">;
function PaginationEllipsis({
className,
children,
...props
}: PaginationEllipsisProps) {
return (
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn(
"mx-3 flex h-10 items-center justify-center text-sm leading-5 font-medium tracking-[-0.2px] text-text-strong",
className,
)}
{...props}
>
{children ?? "..."}
<span className="sr-only">More pages</span>
</span>
);
}
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
paginationLinkVariants,
paginationNavVariants,
};