# Legible Text on Translucent Surfaces

**SHOULD** · **ID:** `content-vibrant-text-on-translucent` · **Category:** content
**Source:** [Emil Kowalski](https://emilkowalski.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/content-vibrant-text-on-translucent

> SHOULD: Text over a `backdrop-filter` surface faces a background whose luminance changes as content scrolls. Raise contrast and weight, nudge letter-spacing up slightly, and keep the opacity/color on a SOLID backing layer while the text itself stays fully opaque — never fade the text with the surface.

Over a blurred or translucent surface, raise text contrast and weight and keep color on a solid layer — flat gray text vanishes as the backdrop shifts

From the vibrancy section of Emil Kowalski's apple-design skill, and it fills the gap between two rules the corpus already has about translucent surfaces. design-impeccable-no-glassmorphism decides WHEN glass is justified (only over content that genuinely scrolls behind it); design-reduced-transparency-contrast handles the accessibility fallback (go solid under prefers-reduced-transparency). Neither says how to make the text on the glass readable once you have decided to keep it — and that is the hard part, because a `backdrop-filter` surface shows whatever is behind it, so the luminance under any given word changes as the user scrolls. A muted gray tuned against one section of the page becomes unreadable over the next. Apple's answer is "vibrancy": foreground text that adapts to what it sits on. On the web you approximate it deliberately — raise the contrast (secondary text on glass should read closer to primary than it would on a solid card), add a touch of weight, and nudge letter-spacing up a hair so thin strokes survive the blur. The load-bearing detail is the last clause: keep any color and the opacity on a SOLID backing layer behind the text, and render the text itself fully opaque on top — never fade the text with the surface, or it dims exactly where the background is busiest. Related: content-impeccable-dark-mode-text-compensation (the same weight/spacing compensation for light-on-dark), design-minimum-contrast.

## Rule snippet

```tsx
.glass { background: rgba(20,18,28,0.55); backdrop-filter: blur(8px); }
.glass p { color: #fff; font-weight: 600; letter-spacing: 0.01em; }
```

## Bad — do not do this

`content-vibrant-text-on-translucent-bad`

```tsx
export function VibrantTextOnTranslucentBad() {
  return (
    <div className="w-full max-w-sm">
      <div className="relative overflow-hidden rounded-lg">
        {/* Busy, high-variance backdrop the glass sits over */}
        <div
          className="absolute inset-0"
          style={{
            background:
              'linear-gradient(120deg, #f9d423 0%, #ff4e50 35%, #1f1c2c 60%, #4ca1af 100%)',
          }}
        />
        {/* Glass panel with flat, translucent gray text */}
        <div
          className="relative m-4 rounded-lg p-4"
          style={{ backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)', background: 'rgba(255,255,255,0.12)' }}
        >
          <p style={{ color: 'rgba(60,60,60,0.65)', fontWeight: 400 }}>Weekly report ready</p>
          <p className="mt-1 text-sm" style={{ color: 'rgba(60,60,60,0.55)', fontWeight: 400 }}>
            12,480 events processed
          </p>
        </div>
      </div>
      <p className="mt-4 text-xs text-destructive">
        Flat translucent gray disappears wherever the backdrop goes light or dark behind it.
      </p>
    </div>
  );
}
```

## Good — do this

`content-vibrant-text-on-translucent-good`

```tsx
export function VibrantTextOnTranslucentGood() {
  return (
    <div className="w-full max-w-sm">
      <div className="relative overflow-hidden rounded-lg">
        {/* Same busy backdrop */}
        <div
          className="absolute inset-0"
          style={{
            background:
              'linear-gradient(120deg, #f9d423 0%, #ff4e50 35%, #1f1c2c 60%, #4ca1af 100%)',
          }}
        />
        {/* Glass panel: opacity lives on a dark solid backing, text stays fully opaque on top */}
        <div
          className="relative m-4 rounded-lg p-4"
          style={{ backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)', background: 'rgba(20,18,28,0.55)' }}
        >
          <p style={{ color: '#ffffff', fontWeight: 600, letterSpacing: '0.01em' }}>Weekly report ready</p>
          <p className="mt-1 text-sm" style={{ color: 'rgba(255,255,255,0.85)', fontWeight: 500, letterSpacing: '0.01em' }}>
            12,480 events processed
          </p>
        </div>
      </div>
      <p className="mt-4 text-xs text-success">
        Opaque text, more weight and contrast, and the opacity moved onto a solid backing — legible over any backdrop.
      </p>
    </div>
  );
}
```

## References

- [Emil Kowalski — apple-design SKILL.md](https://raw.githubusercontent.com/emilkowalski/skills/main/skills/apple-design/SKILL.md)
- [Apple HIG — Materials (vibrancy)](https://developer.apple.com/design/human-interface-guidelines/materials)
- [MDN — backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter)
