Link card
A card that is one link, with a title, a line of text and an arrow top right.
npx shadcn@latest add https://ui.goodtabs.co/r/link-card.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/link-card
One card. Hover fills it softly and darkens the arrow; focus rings the card.
A grid of four
Each card with a leading icon, in a two-column grid.
External
Opens in a new tab, with rel set and a note for screen readers.
Usage
import { LinkCard } from "@/components/ui/link-card"
<LinkCard href="/docs/installation" title="Installation" description="Add the registry and install any item with one command." />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| href | string | UrlObject | Where the card goes. Passed to next/link. | |
| title | string | The card heading. | |
| description | string | One or two lines under the title. | |
| icon | ReactNode | Optional 16px icon above the title. | |
| external | boolean | false | Opens in a new tab with rel="noopener noreferrer" and a sr-only note. |
Source
link-card/link-card.tsx
import * as React from "react";
import Link from "next/link";
import { ArrowUpRightIcon } from "lucide-react";
import { cn } from "@/lib/utils";
type LinkCardProps = Omit<React.ComponentProps<typeof Link>, "title" | "children"> & {
/** The card heading. */
title: string;
/** One or two lines under the title. */
description?: string;
/** Optional icon above the title, 16px, foreground. */
icon?: React.ReactNode;
/** Open in a new tab with rel="noopener noreferrer" and a screen reader note. */
external?: boolean;
};
/**
* A card that is one link. Title, description and an arrow top right.
* The arrow and a soft fill are the hover signal; focus draws a ring around the whole card.
*/
export function LinkCard({ title, description, icon, external = false, className, ...props }: LinkCardProps) {
return (
<Link
data-slot="link-card"
target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined}
className={cn(
"group/link-card relative flex flex-col gap-1 rounded-lg border bg-card p-4 text-sm transition-colors duration-100 ease-out outline-none hover:bg-accent/40 focus-visible:ring-2 focus-visible:ring-ring/50",
className,
)}
{...props}
>
<ArrowUpRightIcon
aria-hidden="true"
className="absolute top-3 right-3 size-4 text-muted-foreground transition-colors duration-100 ease-out group-hover/link-card:text-foreground group-focus-visible/link-card:text-foreground"
/>
{icon && (
<span aria-hidden="true" className="mb-2 inline-flex text-foreground [&_svg]:size-4">
{icon}
</span>
)}
<span className="pr-6 font-medium text-foreground">{title}</span>
{description && <span className="pr-6 text-muted-foreground">{description}</span>}
{external && <span className="sr-only">(opens in a new tab)</span>}
</Link>
);
}
For AI agents
Hand your agent this page, the markdown at /components/link-card/prompt.md, or the Copy AI prompt button above.
When to use
- A grid of destinations on a docs index or an overview page
- Next steps at the end of a guide, two to four cards wide
- Anywhere a whole surface should be one click, not a card with a button inside
When not to use
- When the card holds other interactive things; nested links and buttons are invalid HTML
- For a short inline link in text; use a plain link or the arrow link
- For content that is not navigation, such as a stat or a notice
Agent notes
- Server component built on next/link, so it needs a Next.js app and a real href; it accepts every Link prop
- The whole card is the anchor: do not put buttons or other links inside it
- Hover is bg-accent/40 and the arrow turns foreground; focus-visible rings the card, there is no other colour
- It has no fixed width: set max-w on the card or put several in a CSS grid