Vela UI Docsvenizia.ai

Pagination

Navigation for moving between pages of content.

npx shadcn@latest add @velaui/pagination
npx shadcn@latest add @velaui/pagination

Examples

  • Default
  • High emphasis
  • Icon nav
  • With ellipsis
  • Disabled boundary
  • High emphasis icon nav

View code

Pagination · Default

Installation

pnpm dlx velaui add @velaui/pagination
pnpm dlx velaui add @velaui/pagination

Code

pagination-default.tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  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>
          <PaginationNext href="#" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

View code

Pagination · High emphasis

Installation

pnpm dlx velaui add @velaui/pagination
pnpm dlx velaui add @velaui/pagination

Code

pagination-high-emphasis.tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  return (
    <Pagination emphasis="high">
      <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>
          <PaginationNext href="#" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

View code

Pagination · Icon nav

Installation

pnpm dlx velaui add @velaui/pagination
pnpm dlx velaui add @velaui/pagination

Code

pagination-icon-nav.tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#" size="icon" aria-label="Previous page" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#" isActive>
            1
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">2</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#" size="icon" aria-label="Next page" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

View code

Pagination · With ellipsis

Installation

pnpm dlx velaui add @velaui/pagination
pnpm dlx velaui add @velaui/pagination

Code

pagination-with-ellipsis.tsx
import {
  Pagination,
  PaginationContent,
  PaginationEllipsis,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">1</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationEllipsis />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#" isActive>
            5
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationEllipsis />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">20</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

View code

Pagination · Disabled boundary

Installation

pnpm dlx velaui add @velaui/pagination
pnpm dlx velaui add @velaui/pagination

Code

pagination-disabled-boundary.tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  return (
    <Pagination>
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#" aria-disabled text="Prev" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#" isActive>
            1
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">2</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">3</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

View code

Pagination · High emphasis icon nav

Installation

pnpm dlx velaui add @velaui/pagination
pnpm dlx velaui add @velaui/pagination

Code

pagination-high-emphasis-icon-nav.tsx
import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  return (
    <Pagination emphasis="high">
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious href="#" size="icon" aria-label="Previous page" />
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">4</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#" isActive>
            5
          </PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationLink href="#">6</PaginationLink>
        </PaginationItem>
        <PaginationItem>
          <PaginationNext href="#" size="icon" aria-label="Next page" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

Usage

import {
  Pagination,
  PaginationContent,
  PaginationItem,
  PaginationLink,
  PaginationNext,
  PaginationPrevious,
} from "@/components/ui/pagination";

export default function Example() {
  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>
          <PaginationNext href="#" />
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}

Props

Pagination

PropTypeDefault
emphasisPaginationEmphasis"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

PropTypeDefault
isActiveboolean—

Takes these and every prop of PaginationLinkProps.

PaginationNext

PropTypeDefault
size"default" | "icon""default"
textstring"Next"

Takes these and every prop of PaginationNavProps.

PaginationPrevious

PropTypeDefault
size"default" | "icon""default"
textstring"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,
};