# Never Nest Cards

**NEVER** · **ID:** `layout-impeccable-nested-cards` · **Category:** layout
**Source:** [impeccable](https://impeccable.style/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/layout-impeccable-nested-cards

> NEVER: Nest a card inside a card — an element counts as card-like when it has (a shadow OR a border) AND (a radius OR a background). Build hierarchy inside a card with padding, one hairline divider, and type weight, not a second chrome layer.

Never put a card inside a card — use padding, dividers, and type weight for internal hierarchy

impeccable defines "card-like" structurally — an element is a card when it has (a shadow OR a border) AND (a radius OR a background). The detector walks an element's ancestors and, when it finds a second card-like box inside a first, reports the innermost offender. The damage is twofold: the border and shadow noise doubles, and the depth hierarchy dies, because you now have two elevation levels that mean nothing relative to each other — the inner card is not "further forward" than its parent, it is just louder. Internal hierarchy inside a card is a job for padding, a single hairline divider, and type weight, not for a second chrome layer. Before the nesting question, though, comes the one nobody asks: should this be a card at all? Every Inc's ce-frontend-design skill supplies the missing test — default to cardless layouts, and allow a card only when it is the container for a user interaction (a clickable item, a draggable unit, a selectable option). If removing the card styling would not hurt comprehension, it should not be a card. That test dissolves most nesting violations at the root: the inner box was never earning its border and shadow, it was just grouping content that padding and a heading already grouped.

## Bad — do not do this

`layout-impeccable-nested-cards-bad`

```tsx
/**
 * Bad: a card inside a card. Each plan row repeats the parent's chrome —
 * border + radius + shadow + background — so the detector's card test
 * ((shadow OR border) AND (radius OR background)) fires on the inner boxes.
 * Two elevation levels that mean nothing relative to each other.
 */
export function ImpeccableNestedCardsBad() {
  const plans = [
    { name: 'Hobby', price: '$0', note: 'Personal projects' },
    { name: 'Pro', price: '$20', note: 'Up to 10 seats' },
    { name: 'Enterprise', price: 'Custom', note: 'SSO and audit logs' },
  ];

  return (
    <div className="w-full max-w-sm space-y-4">
      {/* Outer card */}
      <div className="rounded-xl border border-border bg-card p-4 shadow-md">
        <h3 className="mb-3 text-sm font-semibold text-foreground">Billing</h3>

        <div className="space-y-3">
          {plans.map((plan) => (
            /* Inner card: border + radius + background + shadow, all over again */
            <div
              key={plan.name}
              className="rounded-lg border border-border bg-card p-3 shadow-sm"
            >
              <div className="flex items-baseline justify-between">
                <span className="text-sm font-medium text-foreground">{plan.name}</span>
                <span className="text-sm text-foreground">{plan.price}</span>
              </div>
              <p className="mt-1 text-xs text-muted-foreground">{plan.note}</p>
            </div>
          ))}
        </div>
      </div>

      <p className="text-xs text-error">
        Cards inside a card: three extra borders, three extra shadows, three extra radii. The
        border/shadow noise doubles and the depth hierarchy is destroyed &mdash; the inner boxes
        are not &ldquo;closer&rdquo; than the card, just louder.
      </p>
    </div>
  );
}
```

## Good — do this

`layout-impeccable-nested-cards-good`

```tsx
/**
 * Good: one card, one elevation. Internal hierarchy comes from padding,
 * a single hairline divider between rows, and type weight — no second
 * chrome layer, so nothing satisfies the card test inside the card.
 */
export function ImpeccableNestedCardsGood() {
  const plans = [
    { name: 'Hobby', price: '$0', note: 'Personal projects' },
    { name: 'Pro', price: '$20', note: 'Up to 10 seats' },
    { name: 'Enterprise', price: 'Custom', note: 'SSO and audit logs' },
  ];

  return (
    <div className="w-full max-w-sm space-y-4">
      {/* The only card on screen */}
      <div className="rounded-xl border border-border bg-card p-4 shadow-md">
        <h3 className="mb-1 text-sm font-semibold text-foreground">Billing</h3>

        {/* Flat rows, separated by a 1px divider — no border, radius, or shadow of their own */}
        <div className="divide-y divide-border">
          {plans.map((plan) => (
            <div key={plan.name} className="py-3">
              <div className="flex items-baseline justify-between">
                <span className="text-sm font-medium text-foreground">{plan.name}</span>
                <span className="text-sm text-foreground">{plan.price}</span>
              </div>
              <p className="mt-1 text-xs text-muted-foreground">{plan.note}</p>
            </div>
          ))}
        </div>
      </div>

      <p className="text-xs text-success">
        One card, one elevation. Padding, a hairline divider, and type weight carry the internal
        hierarchy &mdash; so the depth that is left actually means something.
      </p>
    </div>
  );
}
```

## References

- [impeccable.style](https://impeccable.style/)
- [pbakaus/impeccable](https://github.com/pbakaus/impeccable)
- [ce-frontend-design (EveryInc/compound-engineering-plugin)](https://github.com/EveryInc/compound-engineering-plugin)
