Skip to content
    Style: Wireframe

    Blocks · Marketing

    Lift cards

    Two or three cards with icon, title and text; on hover and focus the card lifts and the arrow slides; 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/lift-card.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/lift-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.

    lift-card.classes.ts
    import type { IconName } from '@/components/ui/icon/icon.variants';
    
    /** Lift cards: two or three cards with an icon, a title, a few lines and a words-plus-arrow at the foot. The whole
     *  card is one link; on hover and on keyboard focus it lifts and the arrow slides. Under reduced motion nothing
     *  moves (the border and shadow still change, so the state shows). */
    export const liftCard = {
      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-4 p-0 @[40rem]:grid-cols-3',
      item: 'flex min-w-0',
      card: 'group flex w-full flex-col gap-3 rounded-xl border bg-card p-6 text-card-foreground no-underline transition-[box-shadow,border-color] hover:border-foreground/30 hover:shadow-md focus-visible:border-foreground/30 focus-visible:shadow-md active:bg-pressed/40 motion-safe:transition-[transform,box-shadow,border-color] motion-safe:hover:-translate-y-1 motion-safe:focus-visible:-translate-y-1 motion-safe:active:translate-y-0',
      icon: 'mb-1 shrink-0 text-foreground',
      glyph: 'size-6',
      name: 'text-lg font-semibold leading-snug text-balance',
      text: 'text-base text-pretty text-muted-foreground',
      go: 'mt-auto inline-flex items-center gap-1.5 pt-3 text-sm font-medium text-foreground',
      arrow: 'shrink-0 motion-safe:transition-transform motion-safe:group-hover:translate-x-1 motion-safe:group-focus-visible:translate-x-1',
    };
    
    export type LiftCardItem = { title: string; text: string; href: string; icon: IconName; label: string };
    export type LiftCardContent = { id?: string; label: string; title?: string; items: LiftCardItem[] };
    lift-card.tsx
    import { Icon } from '@/components/ui/icon';
    import { cn } from '@/lib/utils';
    import { liftCard as c, type LiftCardContent } from './lift-card.classes';
    
    export type LiftCardProps = LiftCardContent & { className?: string };
    
    /** Lift cards: the whole card is one link; on hover and focus it lifts and the arrow slides (not under reduced motion). */
    export function LiftCard({ id = 'lift-card', label, title, items, className }: LiftCardProps) {
      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}>
                    <Icon name={it.icon} className={cn(c.icon, c.glyph)} />
                    <span className={c.name}>{it.title}</span>
                    <span className={c.text}>{it.text}</span>
                    <span className={c.go}>{it.label}<Icon name="arrow-right" className={c.arrow} /></span>
                  </a>
                </li>
              ))}
            </ul>
          </div>
        </section>
      );
    }