# Split card (themes.dev UI block)

A card with a photo on one side and a heading, a short text and a primary button on the other; the photo goes on top on a narrow card. Black and white, dark mode, shadcn-compatible; React, Astro and plain HTML.

74% of the 2,073 sites in The UI Index have a card (1,540 sites).

## Install

- React: `npx shadcn@latest add https://www.themes.dev/ui/r/split-card.json`
- Astro: `npx shadcn@latest add https://www.themes.dev/ui/r/astro/split-card.json`
- HTML: https://www.themes.dev/ui/r/html/split-card.html (with https://www.themes.dev/ui/r/html/caseroom.css)

Page: https://www.themes.dev/ui/blocks/split-card/ · Rules for agents: https://www.themes.dev/ui/AGENT.md

## Code

### split-card.astro

```astro
---
import Button from '@/components/ui/button.astro';
import { cn } from '@/lib/utils';
import { splitCard as c, type SplitCardContent } from './split-card.classes';

type Props = SplitCardContent & { class?: string };
const { cards, label = 'Cards', class: className } = Astro.props;
---

<ul aria-label={label} class={cn(c.list, className)}>
  {
    cards.map((card) => (
      <li class={c.item}>
        <article class={c.card}>
          <div class={c.media}>
            <img src={card.image.src} alt={card.image.alt ?? ''} width="1280" height="720" class={c.image} loading="lazy" decoding="async" />
          </div>
          <div class={c.body}>
            <h2 class={c.title}>{card.title}</h2>
            {card.text ? <p class={c.text}>{card.text}</p> : null}
            <Button href={card.action.href} class={c.action}>{card.action.label}</Button>
          </div>
        </article>
      </li>
    ))
  }
</ul>
```

### split-card.classes.ts

```tsx
/** A split card: a photo on one side, a heading, a short text and one primary button on the other. On a narrow
 *  card (a phone) the photo sits on top; from 36rem of card width it goes beside the text. The card sizes itself
 *  (a container), so it also folds when it sits in a narrow column of a wide page. The photo is decorative next to
 *  the heading (alt ""), unless `image.alt` says otherwise. */
export const splitCard = {
  list: 'm-0 flex list-none flex-col gap-6 px-6 py-12',
  item: '@container',
  card: 'grid overflow-hidden rounded-xl border bg-card text-card-foreground @xl:grid-cols-[minmax(0,9fr)_minmax(0,11fr)]',
  media: 'relative aspect-[2/1] max-h-72 overflow-hidden border-b bg-muted @xl:aspect-auto @xl:border-e @xl:border-b-0 @xl:max-h-none @xl:min-h-64',
  image: 'absolute inset-0 size-full object-cover object-left-top dark:brightness-[.85]',
  body: 'flex min-w-0 flex-col items-start gap-4 p-6 @3xl:p-10',
  title: 'text-2xl font-semibold tracking-[-0.02em] text-balance',
  text: 'max-w-prose text-pretty text-muted-foreground',
  action: 'mt-auto',
};

export type SplitCardItem = {
  /** At most 60 characters. */
  title: string;
  /** One or two sentences, at most 200 characters. */
  text?: string;
  image: { src: string; alt?: string };
  action: { href: string; label: string };
};

export type SplitCardContent = {
  /** One to three cards, one under the other. */
  cards: SplitCardItem[];
  /** Names the list of cards for assistive tech. */
  label?: string;
};
```

### split-card.tsx

```tsx
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { splitCard as c, type SplitCardContent } from './split-card.classes';

export type SplitCardProps = SplitCardContent & { className?: string };

/** Cards with a photo on one side and a heading, a short text and a button on the other; the photo goes on top on a narrow card. */
export function SplitCard({ cards, label = 'Cards', className }: SplitCardProps) {
  return (
    <ul aria-label={label} className={cn(c.list, className)}>
      {cards.map((card) => (
        <li className={c.item} key={card.title}>
          <article className={c.card}>
            <div className={c.media}>
              <img src={card.image.src} alt={card.image.alt ?? ''} width={1280} height={720} className={c.image} loading="lazy" decoding="async" />
            </div>
            <div className={c.body}>
              <h2 className={c.title}>{card.title}</h2>
              {card.text ? <p className={c.text}>{card.text}</p> : null}
              <Button href={card.action.href} className={c.action}>{card.action.label}</Button>
            </div>
          </article>
        </li>
      ))}
    </ul>
  );
}
```
