Skip to content
goodtabs UI
BrowseBrowse components

Previous and next

Previous and next page cards for the end of a doc. Either side can be missing.

npx shadcn@latest add https://ui.goodtabs.co/r/prev-next.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/prev-next

Both cards side by side. They stack under sm.

Only next

The first page of a section: the next card keeps the right column.

Long titles

Usage

import { PrevNext } from "@/components/ui/prev-next"

<PrevNext
  prev={{ label: "Installation", href: "/docs/installation" }}
  next={{ label: "Theming", href: "/docs/theming" }}
/>

Props

PropTypeDefaultDescription
prev{ label: string; href: string }The page before this one. Omit on the first page.
next{ label: string; href: string }The page after this one. Omit on the last page.
aria-labelstring"Pages"Accessible name of the nav landmark.

Source

prev-next/prev-next.tsx
import * as React from "react";
import Link from "next/link";
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import { cn } from "@/lib/utils";

export type PrevNextPage = {
  label: string;
  href: string;
};

type PrevNextProps = Omit<React.ComponentProps<"nav">, "children"> & {
  /** The page before this one. Omit on the first page. */
  prev?: PrevNextPage;
  /** The page after this one. Omit on the last page. */
  next?: PrevNextPage;
};

/**
 * Previous and next page cards for the end of a doc. Two bordered links side by side,
 * stacked under sm. Either side can be missing.
 */
export function PrevNext({ prev, next, className, "aria-label": ariaLabel = "Pages", ...props }: PrevNextProps) {
  if (!prev && !next) return null;
  return (
    <nav
      data-slot="prev-next"
      aria-label={ariaLabel}
      className={cn("grid w-full gap-3 sm:grid-cols-2", className)}
      {...props}
    >
      {prev && <PrevNextCard page={prev} direction="prev" />}
      {next && <PrevNextCard page={next} direction="next" className={cn(!prev && "sm:col-start-2")} />}
    </nav>
  );
}

function PrevNextCard({
  page,
  direction,
  className,
}: {
  page: PrevNextPage;
  direction: "prev" | "next";
  className?: string;
}) {
  const next = direction === "next";
  return (
    <Link
      href={page.href}
      rel={next ? "next" : "prev"}
      className={cn(
        "group/prev-next flex min-w-0 flex-col gap-1 rounded-lg border bg-background p-4 transition-colors duration-100 ease-out outline-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring/50",
        next ? "items-end text-right" : "items-start text-left",
        className,
      )}
    >
      <span className="font-mono text-xs tracking-wide text-muted-foreground uppercase group-hover/prev-next:text-foreground/70">
        {next ? "Next" : "Previous"}
      </span>
      <span className="flex w-full min-w-0 items-center gap-1.5 text-sm font-medium text-foreground">
        {!next && <ArrowLeftIcon aria-hidden="true" className="size-3.5 shrink-0" />}
        <span className={cn("min-w-0 truncate", next && "ml-auto")}>{page.label}</span>
        {next && <ArrowRightIcon aria-hidden="true" className="size-3.5 shrink-0" />}
      </span>
    </Link>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/prev-next/prompt.md, or the Copy AI prompt button above.

When to use

  • At the end of a docs or guide page that sits in a sequence
  • Chapters of a long form piece or a course
  • Anywhere the next step is one obvious page

When not to use

  • For numbered pages of a list or table, use pagination
  • For moving between sibling records in an app, use icon buttons in the header
  • When there is no sequence: it renders nothing without prev or next

Agent notes

  • Server component with next/link, so pass real hrefs; compute prev and next from your ordered page list
  • With only next, the card keeps the right column from sm up so the layout does not jump between pages
  • Labels truncate to one line; keep them to the page title, not a description
  • The links carry rel=prev and rel=next for crawlers and readers