Skip to content
    Style: Wireframe

    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).

    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.

    link-card.classes.ts
    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[] };
    link-card.tsx
    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>
      );
    }