Navigation

Pagination

Compact row of navigation targets for paging through results, with first/prev/next/last controls and an active page styled in the quebi brand teal.

navigationpaginationpagerlinks

Default

Prev/next controls wrapping a row of page numbers. Page 3 is active.

With gaps

Truncated ranges use a gap. First/last jump controls bracket the row.

Disabled edges

On the first page, the first/prev controls render as disabled (no href).

With result info

Pair the pager with a summary of the current range.

Showing 21–40 of 248 results

Source

Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.

"use client"

import { cn } from "@/lib/utils"

/**
 * Pagination — quebi design system
 *
 * A row of compact navigation targets for paging through results. Self-contained:
 * built on plain anchors so it carries no sibling dependencies. The active page
 * uses the quebi brand teal; everything else is navigation chrome on the dark
 * surface. Interactive targets are 32px and use the quebi border signature.
 */

const navTargetClasses = [
  "inline-flex h-8 min-w-8 shrink-0 items-center justify-center",
  "rounded-quebi-sm border border-solid",
  "font-sans text-[13px] font-medium leading-none select-none",
  "transition-[color,background-color,border-color] duration-150 ease-out",
  "outline-none focus-visible:ring-2 focus-visible:ring-quebi-brand/50 focus-visible:ring-offset-2 focus-visible:ring-offset-quebi-bg",
].join(" ")

const navTargetInteractive = [
  "cursor-pointer border-cyan-500/20 bg-transparent text-quebi-fg-muted",
  "hover:border-quebi-brand hover:text-quebi-brand hover:bg-white/[0.04]",
].join(" ")

const navTargetDisabled = "cursor-not-allowed border-cyan-500/10 bg-transparent text-quebi-fg-subtle"

const pageTargetClasses = [
  "inline-flex h-8 min-w-8 shrink-0 items-center justify-center px-2",
  "rounded-quebi-sm",
  "font-sans text-[13px] font-medium leading-none tabular-nums select-none",
  "transition-[color,background-color] duration-150 ease-out",
  "outline-none focus-visible:ring-2 focus-visible:ring-quebi-brand/50 focus-visible:ring-offset-2 focus-visible:ring-offset-quebi-bg",
].join(" ")

const pageTargetInteractive = [
  "cursor-pointer text-quebi-fg-muted",
  "hover:bg-white/[0.04] hover:text-white",
].join(" ")

const pageTargetCurrent = [
  "cursor-default bg-quebi-brand text-quebi-bg",
  "aria-[current=page]:bg-quebi-brand aria-[current=page]:text-quebi-bg",
].join(" ")

const Pagination = ({ className, ref, ...props }: React.ComponentProps<"nav">) => (
  <nav
    data-slot="pagination"
    aria-label="Pagination"
    className={cn("mx-auto flex w-full items-center justify-center gap-2", className)}
    ref={ref}
    {...props}
  />
)

const PaginationList = ({ className, ref, ...props }: React.ComponentProps<"ul">) => (
  <ul
    ref={ref}
    data-slot="pagination-list"
    className={cn("flex items-center gap-1", className)}
    {...props}
  />
)

interface PaginationItemProps extends Omit<React.ComponentProps<"a">, "children" | "className"> {
  className?: string
  isCurrent?: boolean
  children?: string | number
}

const PaginationItem = ({ className, isCurrent, children, href, ...props }: PaginationItemProps) => (
  <li>
    <a
      data-slot="pagination-item"
      href={isCurrent ? undefined : href}
      aria-current={isCurrent ? "page" : undefined}
      className={cn(
        pageTargetClasses,
        isCurrent ? pageTargetCurrent : pageTargetInteractive,
        className,
      )}
      {...props}
    >
      {children}
    </a>
  </li>
)

interface PaginationNavProps extends Omit<React.ComponentProps<"a">, "className" | "children"> {
  className?: string
  children?: React.ReactNode
}

const NavLink = ({
  className,
  href,
  children,
  label,
  ...props
}: PaginationNavProps & { label: string }) => {
  const isDisabled = href === undefined
  return (
    <li>
      <a
        data-slot="pagination-item"
        aria-label={label}
        aria-disabled={isDisabled ? true : undefined}
        href={href}
        className={cn(
          navTargetClasses,
          isDisabled ? navTargetDisabled : navTargetInteractive,
          className,
        )}
        {...props}
      >
        {children}
      </a>
    </li>
  )
}

const FirstIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={14}
    height={14}
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={1.75}
    strokeLinecap="round"
    strokeLinejoin="round"
    data-slot="icon"
    aria-hidden="true"
  >
    <path d="m17 18-6-6 6-6M7 6v12" />
  </svg>
)

const PrevIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={14}
    height={14}
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={1.75}
    strokeLinecap="round"
    strokeLinejoin="round"
    data-slot="icon"
    aria-hidden="true"
  >
    <path d="m15 18-6-6 6-6" />
  </svg>
)

const NextIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={14}
    height={14}
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={1.75}
    strokeLinecap="round"
    strokeLinejoin="round"
    data-slot="icon"
    aria-hidden="true"
  >
    <path d="m9 6 6 6-6 6" />
  </svg>
)

const LastIcon = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width={14}
    height={14}
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth={1.75}
    strokeLinecap="round"
    strokeLinejoin="round"
    data-slot="icon"
    aria-hidden="true"
  >
    <path d="m7 6 6 6-6 6M17 6v12" />
  </svg>
)

const PaginationFirst = (props: PaginationNavProps) => (
  <NavLink label="First page" {...props}>
    <FirstIcon />
  </NavLink>
)

const PaginationPrevious = (props: PaginationNavProps) => (
  <NavLink label="Previous page" {...props}>
    <PrevIcon />
  </NavLink>
)

const PaginationNext = (props: PaginationNavProps) => (
  <NavLink label="Next page" {...props}>
    <NextIcon />
  </NavLink>
)

const PaginationLast = (props: PaginationNavProps) => (
  <NavLink label="Last page" {...props}>
    <LastIcon />
  </NavLink>
)

const PaginationGap = ({
  className,
  children = <>&hellip;</>,
  ...props
}: React.ComponentProps<"li">) => (
  <li
    data-slot="pagination-gap"
    aria-hidden
    className={cn(
      "inline-flex h-8 min-w-8 items-center justify-center select-none",
      "font-sans text-[13px] font-medium text-quebi-fg-subtle",
      className,
    )}
    {...props}
  >
    {children}
  </li>
)

const PaginationInfo = ({ className, ...props }: React.ComponentProps<"p">) => (
  <p
    className={cn(
      "text-[13px] text-quebi-fg-muted *:[strong]:font-semibold *:[strong]:text-white",
      className,
    )}
    {...props}
  />
)

export {
  Pagination,
  PaginationFirst,
  PaginationGap,
  PaginationInfo,
  PaginationItem,
  PaginationLast,
  PaginationList,
  PaginationNext,
  PaginationPrevious,
}