Blocks · Marketing
Link cards
Cards that each lead to a collection: icon, title, one line and an arrow that moves on hover; the whole card is one link.
74% of the 2,073 sites in The UI Index have a card (1,540 sites).
Browse by topic
Browse by topic
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ link-card.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ astro/ link-card.json
Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.
import type { IconName } from '@/components/ui/icon/icon.variants';
/** Link cards: a quiet row of cards, each one link to a collection (a topic, a category, a guide). The icon sits in
* a small square, then the title and one line, and the arrow at the end moves a little on hover and focus. The
* whole card is the one link, so there is no empty plate: every card has its icon, title and line. The arrow
* holds still under reduced motion. */
export const linkCard = {
root: '@container bg-background px-6 py-14 text-foreground',
inner: 'mx-auto max-w-6xl',
title: 'mb-8 text-2xl font-semibold tracking-[-0.02em] text-balance',
list: 'grid list-none gap-3 p-0 @lg:grid-cols-2 @4xl:grid-cols-3',
item: 'flex',
card: 'group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4',
icon: 'grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground',
glyph: 'size-5',
body: 'flex min-w-0 flex-1 flex-col gap-0.5',
name: 'text-base font-semibold leading-snug text-balance',
text: 'text-sm leading-snug text-muted-foreground',
arrow: 'shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0',
};
export type LinkCardItem = { title: string; text: string; href: string; icon: IconName };
export type LinkCardContent = { id?: string; label: string; title?: string; items: LinkCardItem[] };import { Icon } from '@/components/ui/icon';
import { cn } from '@/lib/utils';
import { linkCard as c, type LinkCardContent } from './link-card.classes';
export type LinkCardProps = LinkCardContent & { className?: string };
/** Link cards: the whole card is one link (icon, title, one line, an arrow that moves on hover and focus). */
export function LinkCard({ id = 'link-card', label, title, items, className }: LinkCardProps) {
return (
<section className={cn(c.root, className)} id={id} aria-label={title ? undefined : label} aria-labelledby={title ? `${id}-title` : undefined}>
<div className={c.inner}>
{title && <h2 className={c.title} id={`${id}-title`}>{title}</h2>}
<ul className={c.list}>
{items.map((it) => (
<li key={it.href} className={c.item}>
<a href={it.href} className={c.card}>
<span className={c.icon}><Icon name={it.icon} className={c.glyph} /></span>
<span className={c.body}>
<span className={c.name}>{it.title}</span>
<span className={c.text}>{it.text}</span>
</span>
<Icon name="arrow-right" className={c.arrow} />
</a>
</li>
))}
</ul>
</div>
</section>
);
}---
import Icon from '@/components/ui/icon.astro';
import { cn } from '@/lib/utils';
import { linkCard as c, type LinkCardContent } from './link-card.classes';
type Props = LinkCardContent & { class?: string };
const { id = 'link-card', label, title, items, class: className } = Astro.props;
---
<section class={cn(c.root, className)} id={id} aria-label={title ? undefined : label} aria-labelledby={title ? `${id}-title` : undefined}>
<div class={c.inner}>
{title && <h2 class={c.title} id={`${id}-title`}>{title}</h2>}
<ul class={c.list}>
{
items.map((it) => (
<li class={c.item}>
<a href={it.href} class={c.card}>
<span class={c.icon}><Icon name={it.icon} class={c.glyph} /></span>
<span class={c.body}>
<span class={c.name}>{it.title}</span>
<span class={c.text}>{it.text}</span>
</span>
<Icon name="arrow-right" class={c.arrow} />
</a>
</li>
))
}
</ul>
</div>
</section>import type { IconName } from '@/components/ui/icon/icon.variants';
/** Link cards: a quiet row of cards, each one link to a collection (a topic, a category, a guide). The icon sits in
* a small square, then the title and one line, and the arrow at the end moves a little on hover and focus. The
* whole card is the one link, so there is no empty plate: every card has its icon, title and line. The arrow
* holds still under reduced motion. */
export const linkCard = {
root: '@container bg-background px-6 py-14 text-foreground',
inner: 'mx-auto max-w-6xl',
title: 'mb-8 text-2xl font-semibold tracking-[-0.02em] text-balance',
list: 'grid list-none gap-3 p-0 @lg:grid-cols-2 @4xl:grid-cols-3',
item: 'flex',
card: 'group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4',
icon: 'grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground',
glyph: 'size-5',
body: 'flex min-w-0 flex-1 flex-col gap-0.5',
name: 'text-base font-semibold leading-snug text-balance',
text: 'text-sm leading-snug text-muted-foreground',
arrow: 'shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0',
};
export type LinkCardItem = { title: string; text: string; href: string; icon: IconName };
export type LinkCardContent = { id?: string; label: string; title?: string; items: LinkCardItem[] };<section class="@container bg-background px-6 py-14 text-foreground" id="ex-link-card" aria-labelledby="ex-link-card-title">
<div class="mx-auto max-w-6xl">
<h2 class="mb-8 text-2xl font-semibold tracking-[-0.02em] text-balance" id="ex-link-card-title">Browse by topic</h2>
<ul class="grid list-none gap-3 p-0 @lg:grid-cols-2 @4xl:grid-cols-3">
<li class="flex">
<a href="#ex-link-getting-started" class="group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4">
<span class="grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block shrink-0 size-5"><path d="M5 3.5v9l7-4.5z"></path></svg>
</span>
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="text-base font-semibold leading-snug text-balance">Getting started</span>
<span class="text-sm leading-snug text-muted-foreground">Install, set up the tokens, ship a first page.</span>
</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</li>
<li class="flex">
<a href="#ex-link-theming" class="group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4">
<span class="grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block shrink-0 size-5"><path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4zM8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2M3.4 3.4l1.4 1.4M11.2 11.2l1.4 1.4M3.4 12.6l1.4-1.4M11.2 4.8l1.4-1.4"></path></svg>
</span>
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="text-base font-semibold leading-snug text-balance">Theming</span>
<span class="text-sm leading-snug text-muted-foreground">Accent, dark mode and the inverted band.</span>
</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</li>
<li class="flex">
<a href="#ex-link-forms" class="group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4">
<span class="grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block shrink-0 size-5"><path d="M10.5 2.5l3 3-8 8h-3v-3z"></path></svg>
</span>
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="text-base font-semibold leading-snug text-balance">Forms</span>
<span class="text-sm leading-snug text-muted-foreground">Fields, validation and error messages.</span>
</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</li>
<li class="flex">
<a href="#ex-link-navigation" class="group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4">
<span class="grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block shrink-0 size-5"><path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"></path></svg>
</span>
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="text-base font-semibold leading-snug text-balance">Navigation</span>
<span class="text-sm leading-snug text-muted-foreground">Headers, menus, breadcrumbs and paging.</span>
</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</li>
<li class="flex">
<a href="#ex-link-accessibility" class="group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4">
<span class="grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block shrink-0 size-5"><path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM2.5 14.5a5.5 5.5 0 0 1 11 0"></path></svg>
</span>
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="text-base font-semibold leading-snug text-balance">Accessibility</span>
<span class="text-sm leading-snug text-muted-foreground">Focus, targets, motion and contrast.</span>
</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</li>
<li class="flex">
<a href="#ex-link-downloads" class="group flex min-h-14 w-full items-center gap-3 rounded-xl border bg-card p-3 text-card-foreground no-underline transition-[border-color,box-shadow,background-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 active:bg-pressed/40 @xs:gap-4 @xs:p-4">
<span class="grid size-10 shrink-0 place-items-center rounded-lg bg-muted text-foreground">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block shrink-0 size-5"><path d="M8 2.5v8M4.5 7L8 10.5 11.5 7M2.5 13.5h11"></path></svg>
</span>
<span class="flex min-w-0 flex-1 flex-col gap-0.5">
<span class="text-base font-semibold leading-snug text-balance">Downloads</span>
<span class="text-sm leading-snug text-muted-foreground">Source files and the plain HTML copies.</span>
</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="inline-block size-4 shrink-0 text-muted-foreground transition-[transform,color] duration-200 group-hover:translate-x-1 group-hover:text-foreground group-focus-visible:translate-x-1 group-focus-visible:text-foreground motion-reduce:transition-none motion-reduce:group-hover:translate-x-0 motion-reduce:group-focus-visible:translate-x-0"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
</li>
</ul>
</div>
</section>