Glassmorphism with Tailwind CSS: the frosted glass effect without wrecking legibility
“Effects with Tailwind” series · 2 of 4
- Build an animated border with Tailwind CSS
- Glassmorphism without wrecking legibility ← you are here
- Scroll-driven animations
- View transitions in Astro
Glassmorphism is that translucent panel that blurs whatever is behind it, like frosted glass. The first time I used it was so long ago that I don’t remember the project, but I’m sure it came from looking for references, probably on Behance: I saw a card with that effect and wanted to recreate it.
I still like it a lot, although over time I’ve learned it’s delicate. It’s easy for it to hurt accessibility or clash with the look of the site you put it on.
It came back into fashion when Apple introduced Liquid Glass. I see glassmorphism as a much older and simpler predecessor of what Apple showed there, and it’s what you can do on the web today with a couple of CSS properties.
With Tailwind CSS it takes three classes. The harder part is keeping the text on top readable, so this article covers both.
blur and backdrop-blur do different things
People mix them up a lot, so it’s worth clearing up before writing anything:
blur-*blurs the element itself. Put it on the card and the card’s text goes blurry.backdrop-blur-*blurs what’s behind the element. The card stays sharp and the background softens.
For the glass effect you always want the second one, which in CSS is the backdrop-filter property.
There’s a detail that’s easy to forget: backdrop-filter does nothing if the element is opaque. The background has to let some of what’s behind it through, so the bg-white/10 is needed for the effect to work.
The three layers of glass
For a panel to look like glass I need three things:
- A translucent background,
bg-white/10, so you can see through it. - The background blur,
backdrop-blur-md, which is the frosting. - A light border,
border border-white/20, which acts as the edge of the glass.
The border is what people skip most often, and it shows: without it the panel looks like a blurry smudge instead of a piece of glass.
<div class="rounded-2xl border border-white/20 bg-white/10 p-8 backdrop-blur-md">
<p class="font-semibold text-white">Three layers: translucent background, blur and a light border.</p>
</div>
You also need something colorful behind it. On a flat background the effect doesn’t show:
Three layers: translucent background, blur and a light border.
The same panel without backdrop-blur. It looks translucent, but it doesn't look like glass.
The only difference between the two panels is backdrop-blur-md, and it’s the class that makes the first one look like glass and the second one just transparent.
A bit of saturation
Real glass also brightens the color of what you see through it a little. Tailwind has backdrop-saturate-* for that, and you can combine it with the blur:
<div
class="rounded-2xl border border-white/25 bg-white/10 p-8 backdrop-blur-lg backdrop-saturate-150"
>
<p class="font-semibold text-white">With saturation, the color behind feels more vivid.</p>
</div>
Blur only
Blur + saturation
In Tailwind 4 the blur scale goes from backdrop-blur-xs (4px) to backdrop-blur-3xl (64px). For interface panels I stick between md (12px) and xl (24px). Below that it doesn’t read as glass, and above it the background turns into a smear of color.
Where it makes the most sense: a translucent header
Outside of demos, the use that suits it best is a fixed bar that lets you glimpse the content scrolling underneath:
<header
class="sticky top-0 z-50 border-b border-white/10 bg-zinc-900/60 backdrop-blur-lg"
>
<nav class="mx-auto flex max-w-4xl items-center justify-between p-4">
<span class="font-bold text-white">EFEELE</span>
<span class="text-sm text-white/70">Blog</span>
</nav>
</header>
The bg-zinc-900/60 is a dark tint at 60% opacity, and it’s what makes the pattern usable: it limits how much the background behind the text can change. That leads straight to the contrast problem.
Contrast
A glass panel has a catch: the background behind the text changes depending on what passes underneath. If a light image scrolls under your header, the white text disappears.
And it isn’t just a matter of taste. WCAG criterion 1.4.3 requires a minimum contrast of 4.5:1 for normal text, and with backdrop-filter you can’t calculate it in advance because it depends on the content passing behind.
There are three ways to avoid it, from most to least effective:
- Set a minimum opacity. Instead of
bg-white/10, use something likebg-zinc-900/60orbg-white/80. The more opaque it is, the less the background matters and the easier it is to guarantee contrast. You lose some of the effect, but it’s the one that actually solves it. - Use heavier text.
font-semiboldon a translucent panel reads better thanfont-normal, because it copes better with the noise behind it. - Keep glass for decorative things. If the panel holds something people must read (a price, an error message, an instruction), it’s better not to put it on glass. Use it for bars, tags and containers.
My rule is simple: if I can’t guarantee 4.5:1 against the worst possible background, I raise the opacity until I can. This blog’s own header does exactly that: it uses bg-white/90 in light mode and 80% in dark mode, with just backdrop-blur-xs. The effect is subtle, but the menu stays readable whatever scrolls underneath.
Beyond accessibility there’s also the look. I’ve seen sites that overdo it, with glass on every card and every button, and after a while it gets tiring on the eyes. I’ve also seen the opposite: two or three glass panels dropped into a design that has nothing to do with them, with no harmony with the rest of the components. Either way, the effect stops adding anything.
Fallback and performance
Almost every browser supports backdrop-filter today, but leaving a fallback for the ones that don’t costs nothing. Tailwind has the supports-* variant for it:
<div
class="bg-zinc-900/95 supports-[backdrop-filter]:bg-zinc-900/60 supports-[backdrop-filter]:backdrop-blur-lg"
>
<!-- Without backdrop-filter it stays almost opaque, which is exactly what you want -->
</div>
Order matters: the almost opaque background is the default, and only if the browser supports the effect is the opacity lowered and the blur added.
As for performance, backdrop-filter is recalculated every time what’s behind it changes. A glass panel over scrolling content is one of the most expensive CSS effects on low-end phones. It also creates its own stacking context, so it sometimes messes with your z-index.
When I’ve implemented it, I have noticed the impact on performance and on the experience of some users. The blur value is what needs the most care: the bigger the radius, the more work for the browser, and a backdrop-blur-3xl added without thinking can ruin the experience on a modest device. That’s why I use it on two or three elements per page at most, with the lowest blur that still reads as glass.
The next part of the series animates things with scroll and no JavaScript: scroll-driven animations with Tailwind.

