# Glassmorphism Is Not a Default

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

> NEVER: Apply `backdrop-filter: blur()` to ordinary content surfaces (cards, tiles, panels) — it re-blurs everything behind it on every scroll tick, and makes text contrast a property of whatever happens to be scrolling underneath (the same label can measure 8:1 over one band and 1.9:1 over the next), which no test can catch. Reserve blur for chrome that genuinely overlaps scrolling content — a sticky nav, a sheet — and use opaque surfaces everywhere else.

Reserve backdrop blur for a surface that genuinely overlaps scrolling content, and use opaque surfaces everywhere else

The detector flags `backdrop-filter: blur()` (any radius) applied to ordinary content surfaces — cards, tiles, panels — rather than to an overlay. Two costs follow. Paint: the browser must sample and blur everything behind the element on every frame it is composited, so a grid of glass cards re-blurs on each scroll tick. Contrast: the effective color behind translucent text is whatever happens to be scrolling underneath, so the same label can measure 8:1 over a dark band and 1.9:1 over a light one — the ratio is a property of the backdrop, not of your tokens, and cannot be tested. Blur earns its cost in exactly one place: chrome (a sticky nav, a sheet) that overlaps scrolling content, where the effect communicates layering. Even there, Emil Kowalski's apple-design skill draws one hard line inside the exception: "**Never stack a light translucent surface on another** — legibility collapses," because each layer samples the already-blurred layer beneath it and the text ends up floating on mush. And note what this principle does NOT cover: it polices glass on taste and cost grounds, but never asks whether the user has requested less of it — for that, see design-reduced-transparency-contrast.

## Bad — do not do this

`design-impeccable-no-glassmorphism-bad`

```tsx
const BANDS = [
  'bg-white',
  'bg-amber-200',
  'bg-slate-900',
  'bg-white',
  'bg-violet-900',
  'bg-amber-100',
];

export function ImpeccableNoGlassmorphismBad() {
  return (
    <div className="space-y-3">
      <p className="text-xs text-muted-foreground">Scroll inside the panel — watch the glass cards.</p>

      <div className="relative h-56 overflow-hidden rounded-lg bg-gradient-to-br from-fuchsia-600 via-violet-500 to-amber-300">
        <div className="absolute inset-0 overflow-y-auto">
          {BANDS.map((band, i) => (
            <div key={i} className={`h-24 ${band} opacity-80`} />
          ))}
        </div>

        <div className="pointer-events-none absolute inset-x-4 top-4 space-y-2">
          {['Revenue', 'Active seats', 'Churn'].map((label) => (
            <div
              key={label}
              className="rounded-lg border border-white/25 bg-white/10 px-3 py-2 backdrop-blur-lg"
            >
              <p className="text-xs font-semibold text-white">{label}</p>
              <p className="text-xs text-white/70">Updated 2 minutes ago</p>
            </div>
          ))}
        </div>
      </div>

      <p className="text-xs text-error">
        Every card is glass by default. `backdrop-filter: blur()` costs paint time on each scroll frame,
        and the effective contrast of the label depends on whatever happens to be behind it — legible over
        the dark band, unreadable over the white one. Nothing is measurable.
      </p>
    </div>
  );
}
```

## Good — do this

`design-impeccable-no-glassmorphism-good`

```tsx
const ROWS = [
  { label: 'Revenue', value: '$48,120' },
  { label: 'Active seats', value: '1,204' },
  { label: 'Churn', value: '1.8%' },
  { label: 'Expansion', value: '+6.2%' },
  { label: 'Trials', value: '87' },
  { label: 'Support load', value: '12 open' },
];

export function ImpeccableNoGlassmorphismGood() {
  return (
    <div className="space-y-3">
      <p className="text-xs text-muted-foreground">Scroll inside the panel — the cards stay readable.</p>

      <div className="h-56 overflow-y-auto rounded-lg border border-border bg-background">
        <div className="sticky top-0 z-10 border-b border-border bg-background/85 px-3 py-2 backdrop-blur-sm">
          <p className="text-xs font-semibold text-foreground">Overview</p>
        </div>

        <div className="space-y-2 p-3">
          {ROWS.map((row) => (
            <div
              key={row.label}
              className="rounded-lg border border-border bg-card px-3 py-2"
            >
              <p className="text-xs font-semibold text-card-foreground">{row.label}</p>
              <p className="text-sm text-muted-foreground">{row.value}</p>
            </div>
          ))}
        </div>
      </div>

      <p className="text-xs text-success">
        Cards sit on opaque surfaces from the elevation scale, so their contrast is a fixed, testable
        number. The blur is spent once, on the sticky header that genuinely overlaps scrolling content —
        the one place it earns its paint cost.
      </p>
    </div>
  );
}
```

## References

- [impeccable.style](https://impeccable.style/)
- [MDN: backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/backdrop-filter)
- [emilkowalski/skills — apple-design](https://github.com/emilkowalski/skills/tree/main/skills/apple-design)
