DEV Community

Cover image for Glassmorphism Done Right: 8 CSS Rules for Glass UI People Can Actually Read
Adel Abdelaziz Goucem
Adel Abdelaziz Goucem

Posted on

Glassmorphism Done Right: 8 CSS Rules for Glass UI People Can Actually Read

Frosted glass looks great in Dribbble shots. In real apps, it often breaks: text you can barely read, no visible edge, a busy background fighting the content, and a phone that slows down because everything is blurred.

The good news: readable glass is not a secret. It comes down to eight simple rules. Let's build a glass card step by step in plain HTML and CSS.

Prefer video? The full walkthrough (English with Arabic subtitles) is above.

The card we're building

<div class="scene">
  <article class="glass">
    <div class="text-area">
      <p class="label">Next appointment</p>
      <p class="time">10:30</p>
      <p>Wednesday, 11 November</p>
      <p>Dr. Lina Haddad, Cardiology</p>
    </div>
  </article>
</div>
Enter fullscreen mode Exit fullscreen mode

1. Background: glass needs something behind it

On a flat background, the blur has nothing to work with and your "glass" looks like a grey box. Add two or three large, soft colour shapes.

.scene {
  background:
    radial-gradient(at 20% 25%, #3D8BEF, transparent 50%),
    radial-gradient(at 80% 75%, #8D6BFF, transparent 50%),
    radial-gradient(at 60% 20%, #2EC7B4, transparent 40%),
    #A9C8E8;
}
Enter fullscreen mode Exit fullscreen mode

Keep the shapes large and calm. Small, busy patterns turn into noise behind the text.

2. Fill: a white layer at about 30%

.glass {
  background: rgba(255, 255, 255, .30);
}
Enter fullscreen mode Exit fullscreen mode

Too low and the card disappears. Too high and it is just a white box. Somewhere between 20% and 40% usually works, but tune it on your real background.

3. Blur: what turns tinted plastic into glass

.glass {
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
}
Enter fullscreen mode Exit fullscreen mode

backdrop-filter blurs whatever sits behind the element, not the element itself. Around 16 to 24px is the sweet spot: below that the background stays sharp and distracting, above it everything turns to mush.

saturate(180%) keeps the colours behind alive instead of turning grey. And keep the -webkit- prefix: Safari still needs it.

4. Edge: real glass catches light on its rim

.glass {
  border: 1px solid rgba(255, 255, 255, .50);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .70);
  background-image: linear-gradient(135deg,
    rgba(255, 255, 255, .22), transparent 40%);
}
Enter fullscreen mode Exit fullscreen mode

A 1px light border draws the rim, an inset highlight on the top edge adds the reflection, and an optional diagonal sheen (keep it under 25%) finishes the effect.

5. Depth: a soft, tinted shadow

.glass {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .70),
    0 24px 60px rgba(30, 60, 110, .22);
}
Enter fullscreen mode Exit fullscreen mode

Use a shadow tinted with your background colour, not pure black: black shadows look dirty on coloured glass. The bigger the surface, the higher it floats, so give it a larger, softer shadow.

6. Readability: where most glass designs fail

The background moves under the text, so the contrast changes from one spot to another. Test the worst spot, not the best one. Body text needs a contrast ratio of at least 4.5:1 (WCAG AA).

.glass {
  color: #0E2236;      /* not white */
  font-weight: 600;
}
.glass .text-area {
  background: rgba(255, 255, 255, .55);
}
Enter fullscreen mode Exit fullscreen mode

Dark, heavier text plus a slightly stronger fill behind the text area fixes most cases. Never put long paragraphs on glass: keep it to short labels and key numbers.

If your interface is also in Arabic, check it there too. Arabic script often needs a slightly larger size to stay clear:

.glass:lang(ar) { font-size: 1.1em; }
Enter fullscreen mode Exit fullscreen mode

7. Dark mode: don't just invert the colours

Switch the fill to a dark tint around 30% and lower the rim to about 15%. Put every value in CSS variables so one switch changes the whole theme, and respect the user's setting:

:root {
  --glass-fill: rgba(255, 255, 255, .30);
  --glass-rim:  rgba(255, 255, 255, .50);
  --ink: #0E2236;
}
@media (prefers-color-scheme: dark) {
  :root {
    --glass-fill: rgba(12, 24, 40, .30);
    --glass-rim:  rgba(255, 255, 255, .15);
    --ink: #EAF3FA;
  }
}
Enter fullscreen mode Exit fullscreen mode

8. Fallbacks and performance

Some people turn transparency off in their system settings. Give them a solid version:

@media (prefers-reduced-transparency: reduce) {
  .glass {
    background: var(--surface-solid);
    backdrop-filter: none;
  }
}
@supports not (backdrop-filter: blur(1px)) {
  .glass { background: rgba(255, 255, 255, .85); }
}
Enter fullscreen mode Exit fullscreen mode

Blur is also expensive for the phone: every blurred layer costs memory and battery. Keep it to a few surfaces, like the tab bar and sheets, and use solid rows in long lists. And don't animate the blur value itself; move or fade the element instead.

5 mistakes I see all the time

  1. Glass on glass on glass. Stacked blur turns everything muddy. Use one glass surface with solid or almost solid rows inside.
  2. Coloured text on glass. Keep text neutral; use colour for icons and accents.
  3. Thin font weights. Light text disappears on a blurred background. Use 500 or heavier.
  4. Glass buttons. The main action should be solid, so people find it instantly.
  5. The same corner radius everywhere. Outer radius should equal inner radius plus the padding (for example 38 = 14 + 24).

A real example

These rules come from Safaa, a clinic appointment app I designed in English, French and Arabic: glass for the cards and the tab bar, a solid colour for the main button, dark heavy text everywhere, and the same tokens in Arabic and in dark mode.

The full CSS

:root {
  --glass-fill: rgba(255, 255, 255, .30);
  --glass-rim: rgba(255, 255, 255, .50);
  --glass-glow: rgba(255, 255, 255, .70);
  --glass-shadow: rgba(30, 60, 110, .22);
  --text-area: rgba(255, 255, 255, .55);
  --surface-solid: #F4F8FC;
  --ink: #0E2236;
}
@media (prefers-color-scheme: dark) {
  :root {
    --glass-fill: rgba(12, 24, 40, .30);
    --glass-rim: rgba(255, 255, 255, .15);
    --glass-glow: rgba(255, 255, 255, .25);
    --glass-shadow: rgba(0, 0, 0, .45);
    --text-area: rgba(12, 24, 40, .55);
    --surface-solid: #13233A;
    --ink: #EAF3FA;
  }
}
.glass {
  background: var(--glass-fill);
  -webkit-backdrop-filter: blur(20px) saturate(180%);
  backdrop-filter: blur(20px) saturate(180%);
  border: 1px solid var(--glass-rim);
  border-radius: 28px;
  box-shadow:
    inset 0 1px 0 var(--glass-glow),
    0 24px 60px var(--glass-shadow);
  color: var(--ink);
  font-weight: 500;
}
.glass .text-area { background: var(--text-area); }
.glass:lang(ar) { font-size: 1.1em; }
@media (prefers-reduced-transparency: reduce) {
  .glass {
    background: var(--surface-solid);
    backdrop-filter: none;
  }
}
@supports not (backdrop-filter: blur(1px)) {
  .glass { background: var(--surface-solid); }
}
Enter fullscreen mode Exit fullscreen mode

Recap

Colour behind the glass, a fill between 20% and 40%, a blur of 16 to 24px, a 1px light rim, a soft tinted shadow, a contrast of 4.5:1 or more, tokens for dark mode, and fallbacks with only a few blurs.

Which rule do you break most often? Tell me in the comments.

Keep it simple.

Top comments (0)