I started noticing it everywhere. From Apple’s UI to Microsoft’s apps to practically every new app trying to achieve that ultra-premium, futuristic feel. That frosted, blurred surface where you can kind of see through to what’s behind it. It kept showing up.
Then I used it myself. I was building a Flutter app and wanted that premium quality feel, and glassmorphism was the answer. Getting it right took more effort than I expected, but when it finally came together, it looked exactly like I wanted.
And the whole experience left me with a question I couldn’t shake: why is glassmorphism even a thing?
Why does this specific look keep coming back?
Then something happened that kind of answered it. I had Microsoft Teams open on my Mac at work when I noticed the sidebar was glowing purple. Not a spotlight effect, nothing from the appearance settings. I stared at it for a second before I figured that there was a web page open behind it with a purple card, and Teams was literally picking up the color through the glass.
The blur was sampling whatever was underneath in real time.
That’s when it made sense. Glassmorphism isn’t just a look—it’s a surface that responds to its environment.
The sidebar wasn’t glowing purple because someone designed it that way. It was glowing purple because of what was behind it. The glass is alive to its context, and that’s the answer to why it keeps coming back. Because when done right, it’s not decoration but a material that behaves. That behavior is what makes an interface feel premium instead of just styled to look that way.
I’m somewhere in the middle on glassmorphism. I love it when it’s done right. I’ve also seen it go sideways, especially when accessibility gets overlooked, or when the implementation adds complexity without adding value. As a dev, I can tell you it takes real effort to get it right 😌
So let’s get into it. What glassmorphism actually is, why it came back, when to use it, and when to leave it alone. Plus ten real-world examples with CodePen-ready code and the accessibility piece, because that’s where most implementations quietly fall apart.
What Is Glassmorphism? The Three-Property Trick
At its core, glassmorphism is three CSS properties doing a job together:
- background blur (
backdrop-filter) - semi-transparent background (
rgba) - subtle border
That combination creates the frosted glass look. You can see through the surface, but whatever’s behind it is soft and diffused rather than sharp and competing.
The name was coined by designer Michał Malewicz in 2020, but I went down a rabbit hole on this, and the look itself is way older than that.
It’s Not Actually New
When I was looking into this, I was surprised to find it goes all the way back to Windows Vista’s “Aero” in the mid-2000s (those translucent window frames). Apple had its version too, with the early Mac OS X “Aqua” interface (very glossy, very “look what glass can do”)—neither lasted long.

The flat design wave in the 2010s swept it all away, and for years, everything went clean, minimal, no blur, no depth.
What brought it back in the 2020s was really a combination of two things:
- Devices and browsers finally had the horsepower for it (more on that in a second).
- The
backdrop-filterCSS property (the thing that actually does the blur) got proper cross-browser support when Firefox added it in 2022.
Before that, it was Safari-only for the longest time, which made it impractical to rely on. Once Firefox added it, every major browser had it, and it became a real tool instead of a nice-to-have.
Note: Glassmorphism vs. Neumorphism
These two get mixed up because they both blew up around the same time.
Neumorphism is the one where everything looks pressed into or extruded from the background using soft shadows, like a soft plastic surface with embossed buttons.
It looked incredible in design mockups but was nearly unusable in practice due to too low contrast and no clear way to tell what was interactive.
Glassmorphism held up better for actual use because the blur creates genuine visual separation between layers.
The two approaches aren’t even that similar when you dig into them; they just share a moment in design history.

Why Tech Giants Actually Committed to It (Hint: It Wasn’t Just Vibes)
Going back to my Teams moment, it’s important to understand that what I witnessed wasn’t a bug. That was the whole point. The blur was sampling the color from the page behind it in real time, and the sidebar was literally glowing with it.
That’s glassmorphism behaving the way it’s designed to behave, with the surface responding to its environment. It’s not a static look, it’s a live material.
That’s why big companies went all-in. It wasn’t purely aesthetic.
The Hardware Finally Caught Up
Rendering a real-time blur that actually blurs whatever is behind your element as the page scrolls or changes is expensive work for the GPU.
A few years ago, that kind of processing on a phone meant battery drain and choppy scrolling.
Now, most devices handle it without issue. The effect that would have lagged on a mid-range Android from 2018 runs smoothly today.
Browser support caught up, too. backdrop-filter now works across Chrome, Safari, Firefox, and Edge.
Tip 👀
You still want a fallback for older environments, but you’re no longer asking the majority of your users to miss out on the effect.
Flat Design Fatigue Was Real
By the early 2020s, flat design had been everywhere for almost a decade. Clean was great, but everything started looking identical.
Open any SaaS product, and you could swear it came from the same Figma template 😑
Glassmorphism gave depth back. A depth that was not the skeuomorphic “make it look like brushed metal” depth from the early 2000s, but something lighter.
A frosted glass card floating over a gradient background tells you there are layers here. The background is context, the glass surface is where you act. That visual separation does actual work.
Apple Made It Official, And Microsoft Was Already There
At WWDC 2025, Apple unveiled Liquid Glass across iOS 26, macOS Tahoe, and every other platform they ship. From what’s been reported, they went deep into the physics of actual glass to get the lensing and refraction behavior right so the surface responds to motion, light, and what’s underneath it in real time.
It’s essentially the same principle as what I accidentally noticed in Teams, just taken to a completely different level of engineering. As we’ll see later in this post, the response has been mixed, but the signal was clear.
When the biggest design authority in consumer tech builds its entire visual language around something, it’s not a trend anymore, it’s a standard 💁♀️
And Microsoft was already there. Their Acrylic material in Windows 11 uses heavy blur specifically to handle UI that floats over unpredictable backgrounds. You can’t know what’s open behind a sidebar or a context menu, so the blur neutralizes it. That’s a functional decision, not a decorative one.
When Glassmorphism Actually Works (And When It Falls Apart)
Glassmorphism is very seductive in design tools. A card over a gradient looks stunning in Figma.
Then you put real content behind it—real users, real backgrounds—and sometimes it holds up beautifully, and sometimes it completely falls apart.
Where It Earns Its Place
Glass works great with navbars, floating toolbars, mini-players, and map overlays. You want the user to feel like they’re looking through a layer, not at a wall.
A blurred nav over a hero image communicates “the page exists behind this” in a way a solid bar just doesn’t.
Short-form surfaces, like login cards, category chips, notification banners, small stat widgets, work well too. When content is brief, and the relationship to the background is part of the design, glass fits naturally.
A 2–3 field login form over a brand image? That’s a great use case.
A dense 15-field form? Not so much.
Tip 🔥
There’s a use case I think gets underrated, what I’d call depth stabilization. If you have a busy background (pixel art, a gradient mesh, a full-bleed photo), a high-blur glass panel actually calms it down. The blur turns all that visual chaos into diffused atmosphere, and suddenly your text is readable without having to cover the image with a solid overlay.
Where It Falls Apart
High-stakes interfaces, like medical dashboards, emergency tools, and anything where someone is scanning data under pressure, need reliable contrast. Glass introduces a variable: what’s behind the glass changes, which means your contrast ratio changes.
A button that reads fine on a dark background can become almost invisible on a light one. When the consequences of misreading something are real, use solid colors.
Dense inputs are also a bad fit. As are tables, data grids, multi-step forms, or, really, anywhere you need the interface to disappear so the task can come forward.
Blur and translucency add noise to exactly the kind of surface that needs zero noise.
And a practical obstacle: backdrop-filter is GPU-heavy. On older or lower-end phones, it causes real lag when you scroll.
I’ve seen it on hardware that wasn’t even that old. You end up degrading the experience for users who can least afford a sluggish interface, which is the opposite of what good design is supposed to do.
Tip: Always give browsers a solid fallback before adding the glass.
.glass-card {
background: rgba(255, 255, 255, 0.9); /* solid fallback */
}
@supports (backdrop-filter: blur(10px)) {
.glass-card {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
}Note: The
@supportscheck is your safety net. Users on browsers or devices that can’t handlebackdrop-filterget a clean, readable solid background instead of invisible content, or jank.
10 Real-World Glassmorphism Examples (With Code)
Alright, let’s get into the good stuff. A real-world implementation inspires each example and has something specific I’d like to point out.
It’s not just “look how pretty” 😌
The HTML and CSS are ready in CodePen for anyone who wants to tinker.
1. Apple Control Center: Glass That Responds to the World
Apple’s Control Center in iOS 26 isn’t just a panel that looks like glass—it behaves like glass.
The surface reflects and refracts the wallpaper behind it in real time, shifting as content changes and responding to motion. Apple calls this “lensing.” It’s when the glass element distorts light around its edges, the way real physical glass would.
That’s the same principle I accidentally stumbled on with the Teams sidebar, just engineered to a completely different standard.
Codepen: Glassmorphism: Apple Control Center
<div class="cc-background">
<div class="cc-panel">
<div class="cc-grid">
<div class="cc-tile">
<span class="cc-icon">✈</span>
<span class="cc-label">Airplane</span>
</div>
<div class="cc-tile active">
<span class="cc-icon">📶</span>
<span class="cc-label">Wi-Fi</span>
</div>
<div class="cc-tile">
<span class="cc-icon">🔵</span>
<span class="cc-label">Bluetooth</span>
</div>
<div class="cc-tile active">
<span class="cc-icon">🔆</span>
<span class="cc-label">Brightness</span>
</div>
</div>
</div>
</div>The border on .cc-panel is barely there, and that’s intentional. A more opaque border would make the panel feel boxed in and solid. This near-invisible border gives it a floating quality, allowing the panel to exist slightly above the background rather than sitting on top of it.
Note ⚠️
The difference between0.18and0.5opacity on that border is the difference between glass and a frosted rectangle.
* { box-sizing: border-box; margin: 0; padding: 0; }
.cc-background {
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 40%, #0f3460 70%, #533483 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', sans-serif;
}
.cc-panel {
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(30px) saturate(180%);
-webkit-backdrop-filter: blur(30px) saturate(180%);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.18);
padding: 20px;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
width: 260px;
}
.cc-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.cc-tile {
background: rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 18px 14px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
cursor: pointer;
transition: background 0.2s ease, transform 0.15s ease;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.cc-tile.active {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.3);
}
.cc-tile:hover {
background: rgba(255, 255, 255, 0.18);
transform: scale(1.03);
}
.cc-icon {
font-size: 22px;
}
.cc-label {
font-size: 11px;
color: rgba(255, 255, 255, 0.85);
font-weight: 500;
letter-spacing: 0.2px;
}What gives the glass its vibrancy is the saturate(180%) alongside the blur. It allows the background colors to bleed through more richly, which makes the transparency feel luxurious rather than washed out.


2. Microsoft Acrylic Context Menu: Blur as a Stability Tool
Microsoft’s Acrylic material in Windows 11 uses a high blur radius to solve a specific design problem: context menus appear over whatever the user has open. That could be a bright document, a dark video, or a busy website.
A solid-colored menu looks fine against one background and completely wrong against another. The blur neutralizes whatever’s underneath, so the menu is readable no matter what.
The best way to see this in action is to look at both states side by side.
- Left side: menu without blur. The background color starts bleeding into the text area, making the menu feel unstable.
- Right side: Acrylic approach with heavy blur. The background is acknowledged but neutralized.
Codepen: Glassmorphism: Microsoft Acrylic Context Menu
<div class="split-demo">
<!-- LEFT: No blur — background competes with the menu -->
<div class="split-side">
<p class="split-label">Without Blur</p>
<div class="split-bg left-bg">
<div class="menu-no-blur">
<div class="menu-item"><span class="menu-icon">📋</span> Copy</div>
<div class="menu-item"><span class="menu-icon">✂️</span> Cut</div>
<div class="menu-item"><span class="menu-icon">📌</span> Paste</div>
<div class="menu-divider"></div>
<div class="menu-item"><span class="menu-icon">🗑️</span> Delete</div>
<div class="menu-item"><span class="menu-icon">✏️</span> Rename</div>
</div>
</div>
</div>
<!-- RIGHT: Heavy blur — background is neutralized -->
<div class="split-side">
<p class="split-label">With Acrylic Blur (40px)</p>
<div class="split-bg right-bg">
<div class="acrylic-menu">
<div class="menu-item"><span class="menu-icon">📋</span> Copy</div>
<div class="menu-item"><span class="menu-icon">✂️</span> Cut</div>
<div class="menu-item"><span class="menu-icon">📌</span> Paste</div>
<div class="menu-divider"></div>
<div class="menu-item"><span class="menu-icon">🗑️</span> Delete</div>
<div class="menu-item"><span class="menu-icon">✏️</span> Rename</div>
</div>
</div>
</div>
</div>* { box-sizing: border-box; margin: 0; padding: 0; }
.split-demo {
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 1fr;
font-family: 'Segoe UI', system-ui, sans-serif;
}
.split-side {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 40px 20px;
}
.split-label {
font-size: 12px;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
font-family: 'Segoe UI', system-ui, sans-serif;
}
/* Same busy background on both sides */
.split-bg {
width: 100%;
max-width: 380px;
min-height: 360px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.left-bg {
background:
radial-gradient(circle at 30% 40%, rgba(255, 100, 50, 0.6) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, rgba(50, 150, 255, 0.5) 0%, transparent 45%),
linear-gradient(135deg, #0078d4 0%, #106ebe 100%);
}
.right-bg {
background:
radial-gradient(circle at 30% 40%, rgba(255, 100, 50, 0.6) 0%, transparent 50%),
radial-gradient(circle at 70% 70%, rgba(50, 150, 255, 0.5) 0%, transparent 45%),
linear-gradient(135deg, #0078d4 0%, #106ebe 100%);
}
/* LEFT: No blur — background color punches straight through */
.menu-no-blur {
background: rgba(32, 32, 32, 0.75); /* same tint, no blur */
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
padding: 4px 0;
min-width: 180px;
}
/* RIGHT: Heavy blur — the background is acknowledged but neutralized */
.acrylic-menu {
background: rgba(32, 32, 32, 0.75);
backdrop-filter: blur(40px) saturate(150%);
-webkit-backdrop-filter: blur(40px) saturate(150%);
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
padding: 4px 0;
min-width: 180px;
}
.menu-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 16px;
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
cursor: pointer;
transition: background 0.1s;
}
.menu-item:hover {
background: rgba(255, 255, 255, 0.1);
}
.menu-icon {
font-size: 14px;
opacity: 0.8;
}
.menu-divider {
height: 1px;
background: rgba(255, 255, 255, 0.1);
margin: 4px 0;
}On the left, the background gradients bleed into the menu surface, shifting its perceived color. On the right, backdrop-filter: blur(40px) neutralizes all of that.
It’s the same background, but the menu reads cleanly against it. That’s the functional argument for glass: it doesn’t just look good, it solves a problem that solid colors can’t.
Tip: I encourage you to play with the comparison for effect. The change is subtle and small; the impact is significant. I chose a relatively mild background on purpose to illustrate that it’s not only a technique to employ for crazy designs. Even everyday stylistic choices on “simple” UIs can make a difference.
3. AnyDistance Workout Selector: Pill-Like Glass on Dark
AnyDistance is a fitness tracking app that’s a good example of glass used with a lot of restraint. Its panels hardly look like glass at first.
The surface is barely distinct from the background, the borders are almost invisible, and everything sits in this deep, dark blue that makes the whole thing feel cohesive rather than layered.
While the glass effect is there, it doesn’t hit you over the head.
Codepen: Glassmorphism: AnyDistance Workout Selector
<div class="workout-background">
<h2 class="workout-heading">Choose Workout</h2>
<div class="workout-list">
<div class="workout-pill active">
<span class="workout-emoji">🏃</span>
<span class="workout-name">Outdoor Run</span>
<span class="workout-stat">5.2 km avg</span>
</div>
<div class="workout-pill">
<span class="workout-emoji">🚴</span>
<span class="workout-name">Cycling</span>
<span class="workout-stat">18 km avg</span>
</div>
<div class="workout-pill">
<span class="workout-emoji">🏊</span>
<span class="workout-name">Swimming</span>
<span class="workout-stat">1.5 km avg</span>
</div>
<div class="workout-pill">
<span class="workout-emoji">🏋️</span>
<span class="workout-name">Strength</span>
<span class="workout-stat">45 min avg</span>
</div>
</div>
</div>* { box-sizing: border-box; margin: 0; padding: 0; }
.workout-background {
min-height: 100vh;
background: radial-gradient(ellipse at 30% 20%, #1e3a5f 0%, #0d1b2a 50%, #060d16 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px 20px;
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Rounded', sans-serif;
}
.workout-heading {
color: rgba(255, 255, 255, 0.95);
font-size: 22px;
font-weight: 700;
letter-spacing: -0.3px;
margin-bottom: 24px;
}
.workout-list {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-width: 340px;
}
.workout-pill {
background: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(16px) saturate(130%);
-webkit-backdrop-filter: blur(16px) saturate(130%);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08);
padding: 16px 20px;
display: flex;
align-items: center;
gap: 14px;
cursor: pointer;
transition: all 0.2s ease;
}
.workout-pill.active {
background: rgba(99, 179, 237, 0.18);
border-color: rgba(99, 179, 237, 0.35);
box-shadow: 0 0 20px rgba(99, 179, 237, 0.12);
}
.workout-pill:hover:not(.active) {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.15);
}
.workout-emoji {
font-size: 22px;
flex-shrink: 0;
}
.workout-name {
flex: 1;
color: rgba(255, 255, 255, 0.9);
font-size: 15px;
font-weight: 600;
}
.workout-stat {
color: rgba(255, 255, 255, 0.45);
font-size: 12px;
font-weight: 500;
}The active state uses a blue tint (rgba(99, 179, 237, 0.18)) rather than just bumping opacity. That’s a better pattern since it gives the selection a color identity without losing the glass quality.
It’s a small decision that makes the whole thing feel more polished.
Tip 💯
When working with selected or active states on glass surfaces, reach for a color tint instead of just increasing opacity. Making an item look more opaque means losing the glass quality so it starts looking like a solid element. Adding a color hue at low opacity (like above) keeps the glass feel while still making the selection obvious.
4. The General Intelligence Co. Homepage: Glass as a Readability Fix
The General Intelligence Co. uses glass panels over a vibrant pixel-art skyline background with the blur cranked up enough that the pixel art completely dissolves into atmosphere.
What’s left is this rich, colorful backdrop that the text sits over, though it’s not perfectly clean. The white text is readable, but against those vibrant, blurred colors, it does take some effort.

You notice it most in the nav area at the top right, where there’s no glass backing at all—just white text over the raw background. That’s where the struggle of readability on busy backgrounds really shows itself.


Still, the main content panels demonstrate the technique well.
Note: This is based on the site as it appeared at the time of writing. It’s worth checking the current version at General Intelligence Co. since it may have evolved.
The code below recreates that specific relationship between blur and tint. The background is intentionally packed with sharp lighting detail and saturated color so you can see what each layer is contributing.
Codepen: Glassmorphism: The General Intelligence Co. Homepage
<div class="gic-scene">
<div class="gic-city"></div>
<main class="gic-layout">
<section class="gic-glass hero">
<p class="gic-kicker">Research Lab</p>
<h1 class="gic-title">
Building General<br />
Intelligence
</h1>
<p class="gic-copy">
We study the foundations of machine reasoning, abstraction,
and failure under distribution shift.
</p>
</section>
<section class="gic-glass">
<p class="gic-kicker">Latest Paper</p>
<h2 class="gic-subtitle">
Chain-of-Thought Breaks Under Distribution Shift
</h2>
</section>
<section class="gic-glass">
<p class="gic-kicker">Team</p>
<h2 class="gic-subtitle">
12 Researchers Across 4 Countries
</h2>
</section>
</main>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--bg: #04050b;
--glass-tint: rgba(10, 12, 22, 0.42);
--glass-border: rgba(255, 255, 255, 0.16);
--glass-highlight: rgba(255, 255, 255, 0.08);
--text-main: rgba(255, 255, 255, 0.96);
--text-soft: rgba(255, 255, 255, 0.72);
}
body {
background: var(--bg);
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
color: white;
}
/*
The background intentionally contains:
- high saturation
- sharp micro detail
- hard contrast transitions
This is critical.
The glass panels should FAIL without:
- backdrop blur
- dark tint
Both are required together.
*/
.gic-scene {
position: relative;
min-height: 100vh;
overflow: hidden;
isolation: isolate;
background:
radial-gradient(circle at top, rgba(60, 70, 120, 0.18), transparent 45%),
var(--bg);
}
/* Atmospheric neon skyline */
.gic-city {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 18% 78%, rgba(255, 0, 128, 0.95), transparent 20%),
radial-gradient(circle at 82% 74%, rgba(0, 220, 255, 0.92), transparent 18%),
radial-gradient(circle at 52% 88%, rgba(116, 0, 255, 0.9), transparent 24%),
radial-gradient(circle at 42% 26%, rgba(255, 170, 0, 0.7), transparent 16%),
linear-gradient(#0c1020 0 0) 6% 100% / 11% 46% no-repeat,
linear-gradient(#070a18 0 0) 22% 100% / 10% 62% no-repeat,
linear-gradient(#040816 0 0) 38% 100% / 15% 76% no-repeat,
linear-gradient(#08131a 0 0) 58% 100% / 13% 56% no-repeat,
linear-gradient(#13061a 0 0) 76% 100% / 14% 44% no-repeat,
linear-gradient(
to bottom,
#090b12 0%,
#04050b 100%
);
}
/*
Sharp pixel windows.
These MUST remain crisp underneath the glass.
Otherwise removing blur won't visibly matter.
*/
.gic-city::before {
content: "";
position: absolute;
inset: 0;
background:
repeating-linear-gradient(
to right,
rgba(255, 228, 120, 0.95) 0 2px,
transparent 2px 18px
),
repeating-linear-gradient(
to right,
rgba(255, 80, 180, 0.9) 0 2px,
transparent 2px 14px
),
repeating-linear-gradient(
to right,
rgba(120, 220, 255, 0.9) 0 2px,
transparent 2px 16px
);
background-size:
100% 14px,
100% 12px,
100% 16px;
background-position:
0 60%,
0 42%,
0 50%;
background-repeat: repeat-x;
opacity: 0.9;
mask-image: linear-gradient(
to top,
black 0%,
black 48%,
transparent 72%
);
-webkit-mask-image: linear-gradient(
to top,
black 0%,
black 48%,
transparent 72%
);
}
/* subtle atmosphere bloom */
.gic-city::after {
content: "";
position: absolute;
inset: 0;
background:
radial-gradient(circle at 20% 80%, rgba(255, 0, 128, 0.28), transparent 30%),
radial-gradient(circle at 80% 76%, rgba(0, 220, 255, 0.24), transparent 26%),
radial-gradient(circle at 55% 90%, rgba(140, 0, 255, 0.24), transparent 32%);
filter: blur(50px);
transform: scale(1.08);
}
.gic-layout {
position: relative;
z-index: 2;
width: min(760px, calc(100% - 40px));
margin: 0 auto;
padding: 72px 0;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
/*
Try commenting out:
- backdrop-filter
OR
- background
You'll immediately see WHY both matter.
*/
.gic-glass {
position: relative;
background: var(--glass-tint);
backdrop-filter:
blur(30px)
saturate(180%);
-webkit-backdrop-filter:
blur(30px)
saturate(180%);
border: 1px solid var(--glass-border);
border-radius: 22px;
padding: 28px;
overflow: hidden;
box-shadow:
0 12px 40px rgba(0, 0, 0, 0.35),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
/* glossy top light */
.gic-glass::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
180deg,
rgba(255, 255, 255, 0.16) 0%,
rgba(255, 255, 255, 0.05) 18%,
transparent 42%
);
pointer-events: none;
}
/* edge refraction */
.gic-glass::after {
content: "";
position: absolute;
inset: 1px;
border-radius: inherit;
border: 1px solid rgba(255, 255, 255, 0.05);
pointer-events: none;
}
.gic-glass.hero {
grid-column: 1 / -1;
padding: 36px;
}
.gic-kicker {
margin-bottom: 14px;
color: rgba(180, 220, 255, 0.82);
font-size: 11px;
letter-spacing: 0.22em;
text-transform: uppercase;
}
.gic-title {
margin-bottom: 16px;
font-size: clamp(2.4rem, 6vw, 4.4rem);
line-height: 1.02;
font-weight: 500;
color: var(--text-main);
text-wrap: balance;
}
.gic-subtitle {
font-size: 1rem;
line-height: 1.55;
font-weight: 400;
color: rgba(255, 255, 255, 0.9);
}
.gic-copy {
max-width: 52ch;
font-size: 0.98rem;
line-height: 1.75;
color: var(--text-soft);
}
@media (max-width: 700px) {
.gic-layout {
grid-template-columns: 1fr;
padding: 36px 0 52px;
}
.gic-glass.hero {
grid-column: auto;
}
}Tip: The dark tint (
rgba(10, 10, 25, 0.55)) is doing most of the legibility work here. Blur softens the pixel detail, but without the tint, the neon colors underneath still compete with the text. Remove the blur and the sharp detail starts creating visual noise again. The two layers only really work when they’re paired together.
5. Nike After Dark Navigation: Using Glass to Separate Visual Layers
Nike’s “After Dark” campaign is a good case for a specific problem glass solves really well: what do you do when your background is deliberately loud?
The “JUST DO IT” watermark is huge because it’s meant to be seen. Without something separating the nav from that background text, you’d have navigation links competing directly with oversized decorative type. This is what the glass nav handles. You read the nav first while the background stays in its place.
The key thing to look at in this example is the contrast between the nav area and the rest of the page.
The watermark text is still clearly visible behind the navigation—intentionally so. But once your eye hits the nav, the glass layer diffuses and softens everything underneath it just enough to establish hierarchy. That’s the whole point.
Codepen: Glassmorphism: Nike After Dark Navigation
<div class="nike-background">
<!-- Atmospheric lighting -->
<div class="nike-glow nike-glow-left"></div>
<div class="nike-glow nike-glow-right"></div>
<!-- Loud background typography -->
<div class="nike-watermark">
JUST<br>
DO<br>
IT
</div>
<!-- Glass navigation -->
<nav class="nike-nav">
<div class="nike-logo">
<svg viewBox="0 0 24 24" fill="none">
<path d="M21 4L7 15l-5-3.5 1.5-1.5L7 12.5 19.5 2z" fill="white"/>
</svg>
</div>
<ul class="nike-links">
<li><a href="#">New Releases</a></li>
<li><a href="#">Men</a></li>
<li><a href="#">Women</a></li>
<li><a href="#">Kids</a></li>
<li><a href="#">Sale</a></li>
</ul>
<div class="nike-actions">
<button aria-label="Search">⌕</button>
<button aria-label="Cart">◔</button>
</div>
</nav>
<!-- Hero -->
<section class="nike-hero">
<p class="nike-sub">AFTER DARK TOUR 2026</p>
<h1 class="nike-headline">
Run<br>
The Dark
</h1>
<button class="nike-btn">
Shop Collection
</button>
</section>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #050505;
font-family: Inter, sans-serif;
}
.nike-background {
position: relative;
min-height: 100vh;
overflow: hidden;
background:
radial-gradient(circle at 20% 20%, rgba(117, 0, 255, 0.22), transparent 35%),
radial-gradient(circle at 75% 30%, rgba(0, 214, 201, 0.12), transparent 35%),
radial-gradient(circle at 50% 100%, rgba(255, 255, 255, 0.04), transparent 50%),
#040404;
}
/* Atmospheric glows */
.nike-glow {
position: absolute;
border-radius: 999px;
filter: blur(90px);
opacity: 0.7;
pointer-events: none;
}
.nike-glow-left {
width: 380px;
height: 380px;
background: rgba(94, 0, 255, 0.25);
top: -120px;
left: -100px;
}
.nike-glow-right {
width: 420px;
height: 420px;
background: rgba(0, 255, 204, 0.12);
right: -120px;
top: 120px;
}
/* Loud decorative typography */
.nike-watermark {
position: absolute;
top: -90px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
font-size: clamp(220px, 34vw, 460px);
font-weight: 900;
line-height: 0.72;
letter-spacing: -0.08em;
text-align: center;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.16);
user-select: none;
pointer-events: none;
}
/* Glass nav */
.nike-nav {
position: fixed;
top: 24px;
left: 24px;
right: 24px;
height: 78px;
padding: 0 32px;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 100;
background: rgba(10, 10, 10, 0.42);
backdrop-filter: blur(24px) saturate(160%);
-webkit-backdrop-filter: blur(24px) saturate(160%);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
inset 0 1px rgba(255, 255, 255, 0.08),
0 10px 40px rgba(0, 0, 0, 0.35);
border-radius: 22px;
}
/*
Remove backdrop-filter and the nav immediately
starts fighting with the watermark behind it.
That's the actual lesson here.
*/
.nike-logo svg {
width: 58px;
height: 22px;
}
.nike-links {
display: flex;
gap: 36px;
list-style: none;
}
.nike-links a {
position: relative;
color: rgba(255, 255, 255, 0.82);
text-decoration: none;
font-size: 12px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
transition:
opacity 0.2s ease,
color 0.2s ease;
}
.nike-links a:hover {
color: #fff;
}
.nike-actions {
display: flex;
align-items: center;
gap: 12px;
}
.nike-actions button {
width: 38px;
height: 38px;
border-radius: 999px;
border: 1px solid rgba(255,255,255,0.08);
background: rgba(255,255,255,0.04);
color: white;
cursor: pointer;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transition: background 0.2s ease;
}
.nike-actions button:hover {
background: rgba(255,255,255,0.08);
}
/* Hero */
.nike-hero {
position: absolute;
left: 72px;
bottom: 72px;
z-index: 2;
}
.nike-sub {
margin-bottom: 18px;
color: #8fffe6;
font-size: 13px;
font-weight: 800;
letter-spacing: 0.32em;
text-transform: uppercase;
}
.nike-headline {
margin-bottom: 36px;
color: white;
font-size: clamp(58px, 9vw, 120px);
font-weight: 900;
line-height: 0.84;
letter-spacing: -0.05em;
text-transform: uppercase;
}
.nike-btn {
height: 54px;
padding: 0 30px;
border: none;
border-radius: 999px;
background: white;
color: black;
font-size: 12px;
font-weight: 800;
letter-spacing: 0.14em;
text-transform: uppercase;
cursor: pointer;
transition:
transform 0.2s ease,
background 0.2s ease;
}
.nike-btn:hover {
background: #8fffe6;
transform: translateY(-1px);
}6. Apple Vision Pro Overlays: Glass in 3D Space
Vision Pro is a different problem entirely. You’re building UI for a headset where the user can literally see the room around them 🤯
Solid panels would just block that out. Glass panels let the environment stay visible while the UI floats in front of it. Don’t be fooled, it’s not an aesthetic decision. This is the only option that makes sense for the medium.
On a regular screen, you can borrow this idea for any card-over-image layout where you want the background to stay part of the composition rather than get covered up.
Codepen: Glassmorphism: Apple Vision Pro Overlays
<div class="vp-scene" id="scene">
<div class="vp-room-bg"></div>
<div class="vp-card primary">
<div class="vp-glass"></div>
<div class="vp-content">
<div class="vp-app-row">
<div class="vp-app-icon">📺</div>
<div class="vp-app-icon">🎵</div>
<div class="vp-app-icon">📷</div>
<div class="vp-app-icon">🗺️</div>
</div>
<div class="vp-toolbar">
<div class="vp-pill-btn active">Watch Now</div>
<div class="vp-pill-btn">Library</div>
<div class="vp-pill-btn">Search</div>
</div>
</div>
</div>
<div class="vp-card secondary">
<div class="vp-glass"></div>
<div class="vp-content small">
<div class="vp-label">SAFARI</div>
<div class="vp-url">apple.com</div>
</div>
</div>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.vp-scene {
height: 100vh;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
perspective: 1200px;
}
/* =========================
BACKGROUND (KEEP SIMPLE)
========================= */
.vp-room-bg {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 20% 30%, rgba(255, 200, 160, 0.35), transparent 45%),
radial-gradient(circle at 80% 70%, rgba(120, 160, 255, 0.22), transparent 50%),
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.06), transparent 60%),
linear-gradient(180deg, #6a5a4d 0%, #2f2a26 60%, #151312 100%);
}
/* adds subtle “environment structure” */
.vp-room-bg::after {
content: "";
position: absolute;
inset: 0;
background-image:
repeating-linear-gradient(
90deg,
rgba(255,255,255,0.03) 0px,
rgba(255,255,255,0.03) 1px,
transparent 1px,
transparent 80px
);
opacity: 0.25;
}
/* =========================
CARD BASE
========================= */
.vp-card {
position: absolute;
transform-style: preserve-3d;
top: 50%;
left: 50%;
}
/* =========================
REAL GLASSMORPHISM CORE
========================= */
.vp-glass {
position: absolute;
inset: 0;
border-radius: inherit;
background: rgba(255, 255, 255, 0.07);
backdrop-filter: blur(28px) saturate(140%);
-webkit-backdrop-filter: blur(28px) saturate(140%);
border: 1px solid rgba(255,255,255,0.18);
box-shadow:
0 12px 30px rgba(0,0,0,0.18),
inset 0 1px 0 rgba(255,255,255,0.25);
}
.vp-glass::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(
135deg,
rgba(255,255,255,0.18),
transparent 55%
);
pointer-events: none;
}
/* =========================
PRIMARY CARD
========================= */
.primary {
width: 320px;
transform:
translate(-50%, -50%)
translate3d(0px, 0px, 80px);}
/* slightly clearer glass */
.primary .vp-glass {
backdrop-filter: blur(30px) saturate(150%);
}
/* =========================
SECONDARY CARD
========================= */
.secondary {
width: 140px;
transform:
translate(-50%, -50%)
translate3d(260px, -80px, -140px)
scale(0.9);}
/* slightly more diffused glass */
.secondary .vp-glass {
backdrop-filter: blur(24px) saturate(130%);
}
/* =========================
CONTENT (CRISP LAYER)
========================= */
.vp-content {
position: relative;
z-index: 2;
padding: 28px;
color: white;
}
.vp-content.small {
padding: 20px;
}
/* =========================
ICONS
========================= */
.vp-app-row {
display: flex;
justify-content: space-around;
margin-bottom: 20px;
}
.vp-app-icon {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
background: rgba(255,255,255,0.08);
border-radius: 14px;
border: 1px solid rgba(255,255,255,0.10);
}
/* =========================
BUTTONS
========================= */
.vp-toolbar {
display: flex;
gap: 8px;
justify-content: center;
}
.vp-pill-btn {
padding: 7px 16px;
border-radius: 20px;
font-size: 12px;
color: rgba(255,255,255,0.78);
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.12);
}
.vp-pill-btn.active {
background: rgba(255,255,255,0.16);
color: white;
}Look at the two cards in this example.
- The primary card (
.vp-floating-card) feels closer because it’s larger, centered, and responds more strongly to movement. - The secondary card (
.vp-depth-card) feels further away because it’s smaller, offset, and less visually dominant.
It’s the glass effect that keeps both cards readable over the same background. You can still see the environment through them, which makes it feel like they exist in one shared space instead of sitting on top of a flat page.
If either card were fully solid, the layout would still read correctly, but the background would be blocked out instead of staying visible through the UI. So you lose the sense that the interface is sitting on top of a real environment instead of replacing it.
7. Rains E-commerce: Glass as Spotlight, Not Wallpaper
Rains, the Danish rainwear brand, uses glassmorphism sparingly, mainly on the nav and the CTA button. The product photography does the heavy lifting.
Here, the glass elements are highlights, not the main event. This is the restraint principle in practice.
Tip: One or two glass elements per viewport is the sweet spot. Any more and the effect becomes visual noise.
Codepen: Glassmorphism: Rains E-commerce
<div class="rains-page">
<div class="rains-bg">
<div class="rains-noise"></div>
</div>
<nav class="rains-nav">
<span class="rains-brand">RAINS</span>
<ul class="rains-menu">
<li>Jackets</li>
<li>Bags</li>
<li>Accessories</li>
</ul>
<span class="rains-cart">🛍 Cart (2)</span>
</nav>
<div class="rains-product">
<div class="rains-img-placeholder">
PRODUCT IMAGE
</div>
<div class="rains-details">
<p class="rains-category">Outerwear</p>
<h2 class="rains-name">Long Jacket W3</h2>
<p class="rains-price">€180</p>
<button class="rains-cta">Add to Bag</button>
</div>
</div>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
list-style: none;
}
.rains-page {
min-height: 100vh;
position: relative;
font-family: Georgia, "Times New Roman", serif;
overflow: hidden;
}
/* =========================
BACKGROUND WITH STRUCTURE
========================= */
.rains-bg {
position: absolute;
inset: 0;
background: linear-gradient(160deg, #8aa3b3 0%, #5f7f92 100%);
}
/* THIS is what makes blur visible */
.rains-noise {
position: absolute;
inset: 0;
background:
linear-gradient(90deg, rgba(255,255,255,0.12) 1px, transparent 1px),
linear-gradient(0deg, rgba(0,0,0,0.08) 1px, transparent 1px),
radial-gradient(circle at 20% 30%, rgba(255,255,255,0.25), transparent 35%),
radial-gradient(circle at 70% 60%, rgba(0,0,0,0.25), transparent 40%);
background-size: 120px 120px, 120px 120px, auto, auto;
opacity: 0.55;
}
/* =========================
NAV — now blur is actually visible
========================= */
.rains-nav {
position: sticky;
top: 0;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 40px;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid rgba(255, 255, 255, 0.18);
}
.rains-brand {
color: white;
letter-spacing: 4px;
}
.rains-menu {
display: flex;
gap: 32px;
}
.rains-menu li {
color: rgba(255,255,255,0.75);
font-size: 13px;
}
.rains-cart {
color: white;
font-size: 13px;
}
/* =========================
CONTENT
========================= */
.rains-product {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1fr 1fr;
min-height: calc(100vh - 60px);
}
.rains-img-placeholder {
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.15);
color: rgba(255,255,255,0.35);
}
.rains-details {
display: flex;
flex-direction: column;
justify-content: center;
padding: 60px 48px;
}
.rains-category {
color: rgba(255,255,255,0.55);
font-size: 10px;
letter-spacing: 3px;
}
.rains-name {
color: white;
font-size: 36px;
margin: 16px 0;
}
.rains-price {
color: rgba(255,255,255,0.8);
margin-bottom: 32px;
}
/* CTA — subtle but now depends on blur */
.rains-cta {
padding: 14px 36px;
background: rgba(255,255,255,0.10);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255,255,255,0.18);
color: white;
font-size: 12px;
letter-spacing: 2px;
text-transform: uppercase;
}Glass on the nav. Glass on the CTA button. Everything else stays flat and grounded around the product imagery. That restraint is what makes the glass stand out rather than blend into the rest of the interface.
8. Robinhood Crypto Widgets: Readable Data Through Glass
Financial data needs to be readable at a glance. If you have someone squinting at a balance because the blur is too aggressive, you’re doing it very wrong 🙅♀️
Robinhood’s crypto widgets use translucent overlays to give the interface a premium feel while keeping numbers sharp. The trick is restraint: dense surfaces, subtle translucency, and low blur instead of exaggerated frosted glass.
Tip: Good financial glass should almost disappear behind the usability.
The easiest way to see the difference is by comparing restrained financial glass against the kind of overdone frosted glass effect that looks impressive in a hero section, but starts hurting scanability once real data is involved.
Codepen: Glassmorphism: Robinhood Crypto Widgets
<div class="robinhood-bg">
<div class="comparison-grid">
<!-- OVERDONE GLASS -->
<div class="demo-column">
<p class="demo-label bad-label">
Overdone Frosted Glass
</p>
<div class="rh-widget bad-glass">
<div class="rh-widget-header">
<span class="rh-coin">₿</span>
<div>
<p class="rh-coin-name">Bitcoin</p>
<p class="rh-coin-ticker">BTC</p>
</div>
<span class="rh-badge up">+2.4%</span>
</div>
<p class="rh-price">$68,420.00</p>
<div class="rh-sparkline">
<svg viewBox="0 0 120 40" preserveAspectRatio="none">
<polyline
points="0,35 20,28 40,32 60,18 80,22 100,10 120,14"
fill="none"
stroke="rgba(0,210,106,0.72)"
stroke-width="2"
/>
</svg>
</div>
</div>
</div>
<!-- RESTRAINED FINANCIAL GLASS -->
<div class="demo-column">
<p class="demo-label good-label">
Restrained Financial Glass
</p>
<div class="rh-widget good-glass">
<div class="rh-widget-header">
<span class="rh-coin">₿</span>
<div>
<p class="rh-coin-name">Bitcoin</p>
<p class="rh-coin-ticker">BTC</p>
</div>
<span class="rh-badge up">+2.4%</span>
</div>
<p class="rh-price">$68,420.00</p>
<div class="rh-sparkline">
<svg viewBox="0 0 120 40" preserveAspectRatio="none">
<polyline
points="0,35 20,28 40,32 60,18 80,22 100,10 120,14"
fill="none"
stroke="rgba(0,210,106,0.92)"
stroke-width="2"
/>
</svg>
</div>
</div>
</div>
</div>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.robinhood-bg {
position: relative;
overflow: hidden;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
background:
linear-gradient(135deg, #0a0f16 0%, #101826 50%, #0a0f16 100%);
font-family:
-apple-system,
BlinkMacSystemFont,
"SF Pro Text",
sans-serif;
}
/* ===================================== */
/* HIGH-FREQUENCY BACKGROUND DETAIL */
/* THIS is what makes blur necessary */
/* ===================================== */
.robinhood-bg::before {
content: "";
position: absolute;
inset: 0;
background-image:
/* market grid */
linear-gradient(
rgba(255,255,255,0.03) 1px,
transparent 1px
),
linear-gradient(
90deg,
rgba(255,255,255,0.03) 1px,
transparent 1px
),
/* chart line */
linear-gradient(
115deg,
transparent 0%,
transparent 40%,
rgba(0,210,106,0.14) 41%,
rgba(0,210,106,0.14) 42%,
transparent 43%
),
/* second chart line */
linear-gradient(
70deg,
transparent 0%,
transparent 62%,
rgba(80,140,255,0.12) 63%,
rgba(80,140,255,0.12) 64%,
transparent 65%
);
background-size:
34px 34px,
34px 34px,
100% 100%,
100% 100%;
opacity: 0.9;
pointer-events: none;
}
/* extra market activity / ticker noise */
.robinhood-bg::after {
content: "";
position: absolute;
inset: 0;
background:
repeating-linear-gradient(
90deg,
rgba(255,255,255,0.02) 0px,
rgba(255,255,255,0.02) 2px,
transparent 2px,
transparent 10px
);
opacity: 0.5;
mix-blend-mode: soft-light;
pointer-events: none;
}
.comparison-grid {
position: relative;
z-index: 2;
width: 100%;
max-width: 760px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 28px;
}
.demo-column {
display: flex;
flex-direction: column;
gap: 12px;
}
.demo-label {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.01em;
}
.bad-label {
color: rgba(255,120,120,0.92);
}
.good-label {
color: rgba(120,255,180,0.92);
}
/* ===================================== */
/* SHARED CARD BASE */
/* ===================================== */
.rh-widget {
position: relative;
min-height: 190px;
border-radius: 18px;
padding: 20px;
}
/* ===================================== */
/* BAD GLASS */
/* ===================================== */
.bad-glass {
background: rgba(255,255,255,0.18);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
border: 1px solid rgba(255,255,255,0.22);
box-shadow:
0 14px 40px rgba(0,0,0,0.28);
}
/* over-softened typography */
.bad-glass .rh-price {
color: rgba(255,255,255,0.66);
}
.bad-glass .rh-coin-name {
color: rgba(255,255,255,0.76);
}
.bad-glass .rh-coin-ticker {
color: rgba(255,255,255,0.52);
}
/* ===================================== */
/* RESTRAINED FINANCIAL GLASS */
/* ===================================== */
.good-glass {
/* IMPORTANT:
translucent enough that background leaks through */
background: rgba(18,22,30,0.38);
/* IMPORTANT:
blur now visibly stabilizes background detail */
backdrop-filter: blur(10px) saturate(145%);
-webkit-backdrop-filter: blur(10px) saturate(145%);
border: 1px solid rgba(255,255,255,0.08);
box-shadow:
0 1px 0 rgba(255,255,255,0.05) inset,
0 10px 30px rgba(0,0,0,0.34);
}
/* comment out backdrop-filter here
and readability noticeably degrades */
.good-glass .rh-price {
color: rgba(255,255,255,0.97);
}
.good-glass .rh-coin-name {
color: rgba(255,255,255,0.92);
}
.good-glass .rh-coin-ticker {
color: rgba(255,255,255,0.42);
}
/* ===================================== */
/* HEADER */
/* ===================================== */
.rh-widget-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
}
/* corrected coin contrast */
.rh-coin {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border-radius: 50%;
font-size: 22px;
font-weight: 700;
color: rgba(255,255,255,0.98);
background:
linear-gradient(
180deg,
rgba(255,255,255,0.10),
rgba(255,255,255,0.04)
);
border: 1px solid rgba(255,255,255,0.08);
box-shadow:
0 1px 0 rgba(255,255,255,0.05) inset;
}
.rh-coin-name {
font-size: 14px;
font-weight: 600;
line-height: 1;
}
.rh-coin-ticker {
margin-top: 4px;
font-size: 11px;
}
/* ===================================== */
/* BADGE */
/* ===================================== */
.rh-badge {
margin-left: auto;
padding: 4px 8px;
border-radius: 999px;
font-size: 12px;
font-weight: 600;
}
.rh-badge.up {
color: #00d26a;
background: rgba(0,210,106,0.10);
}
/* ===================================== */
/* PRICE */
/* ===================================== */
.rh-price {
margin-bottom: 14px;
font-size: 28px;
font-weight: 700;
letter-spacing: -0.04em;
}
/* ===================================== */
/* CHART */
/* ===================================== */
.rh-sparkline {
width: 100%;
height: 42px;
}
.rh-sparkline svg {
width: 100%;
height: 100%;
}A couple of things worth pulling out of the code here:
The restrained version keeps the .rh-price nearly opaque at rgba(255,255,255,0.97). Financial UI is one of the few places where “slightly softer text” starts becoming an actual usability problem.
The other important difference is the surface itself. The restrained widget is more translucent than the overdone version, but the blur is lower and more controlled. That combination matters. The background activity still comes through, but it gets stabilized enough that the data remains easy to scan.
The bad example technically has more glass effect, but it’s doing what overly frosted UI often does, where it diffuses contrast, softens edges, and makes the interface feel atmospheric instead of readable.
9. Superhuman Email: Keeping the Foreground Readable Over Animation
Superhuman’s marketing uses glass panels over animated gradient backgrounds. The panels stay readable because the blur is aggressive enough that the animation underneath becomes softer with minimal shapes or competing text.
It’s a strong example of glass serving mood without fighting the content.

Now, you may be wondering: since the panel is already fairly opaque, does the glass effect actually make a difference?
Yes! And the best way to see it is to compare the same panel with and without backdrop-filter.
This example shows both: on the left, the glow animation bleeds straight through a panel with no blur. On the right, backdrop-filter: blur(40px) turns that same animation into soft, ambient color that the text sits cleanly over.
The right variant turns the same animation into a soft, atmospheric drift that the text sits cleanly over.
Codepen: Glassmorphism: Superhuman Email
<div class="superhuman-bg">
<div class="sh-comparison">
<!-- LEFT -->
<div class="sh-col">
<p class="sh-col-label">Without Blur</p>
<div class="sh-panel-wrapper">
<div class="sh-bg-field">
<div class="sh-grid"></div>
<div class="sh-beam beam-1"></div>
<div class="sh-beam beam-2"></div>
<div class="sh-node node-1"></div>
<div class="sh-node node-2"></div>
</div>
<div class="sh-panel no-blur">
<div class="sh-inbox-header">
<span class="sh-app-name">Superhuman</span>
<span class="sh-badge">Inbox Zero 🎉</span>
</div>
<div class="sh-email-list">
<div class="sh-email-item">
<div class="sh-avatar">S</div>
<div class="sh-email-content">
<p class="sh-email-from">Sarah Chen</p>
<p class="sh-email-subject">Q3 roadmap feedback needed</p>
</div>
<span class="sh-time">9:14am</span>
</div>
<div class="sh-email-item">
<div class="sh-avatar sh-read">M</div>
<div class="sh-email-content">
<p class="sh-email-from sh-read">Marcus Webb</p>
<p class="sh-email-subject sh-read">Hiring decision notes</p>
</div>
<span class="sh-time sh-read">Yesterday</span>
</div>
</div>
</div>
</div>
</div>
<!-- RIGHT -->
<div class="sh-col">
<p class="sh-col-label">With Blur</p>
<div class="sh-panel-wrapper">
<div class="sh-bg-field">
<div class="sh-grid"></div>
<div class="sh-beam beam-1"></div>
<div class="sh-beam beam-2"></div>
<div class="sh-node node-1"></div>
<div class="sh-node node-2"></div>
</div>
<div class="sh-panel with-blur">
<div class="sh-inbox-header">
<span class="sh-app-name">Superhuman</span>
<span class="sh-badge">Inbox Zero 🎉</span>
</div>
<div class="sh-email-list">
<div class="sh-email-item">
<div class="sh-avatar">S</div>
<div class="sh-email-content">
<p class="sh-email-from">Sarah Chen</p>
<p class="sh-email-subject">Q3 roadmap feedback needed</p>
</div>
<span class="sh-time">9:14am</span>
</div>
<div class="sh-email-item">
<div class="sh-avatar sh-read">M</div>
<div class="sh-email-content">
<p class="sh-email-from sh-read">Marcus Webb</p>
<p class="sh-email-subject sh-read">Hiring decision notes</p>
</div>
<span class="sh-time sh-read">Yesterday</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
@keyframes beamMove {
0%, 100% {
transform: translateX(0) rotate(-12deg);
}
50% {
transform: translateX(42px) rotate(-12deg);
}
}
@keyframes nodeFloat {
0%, 100% {
transform: translate(0, 0);
}
50% {
transform: translate(22px, -18px);
}
}
.superhuman-bg {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
background: radial-gradient(circle at top, #171428 0%, #06070a 55%);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
/* -------------------------------- */
/* LAYOUT */
/* -------------------------------- */
.sh-comparison {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 28px;
width: 100%;
max-width: 920px;
}
.sh-col {
display: flex;
flex-direction: column;
gap: 12px;
}
.sh-col-label {
font-size: 11px;
letter-spacing: 1.6px;
text-transform: uppercase;
color: rgba(255,255,255,0.45);
}
/* -------------------------------- */
/* WRAPPER */
/* -------------------------------- */
.sh-panel-wrapper {
position: relative;
height: 280px;
border-radius: 26px;
overflow: hidden;
box-shadow: 0 30px 80px rgba(0,0,0,0.6);
}
/* -------------------------------- */
/* SHARED ANIMATED FIELD (KEY FIX) */
/* -------------------------------- */
.sh-bg-field {
position: absolute;
inset: 0;
overflow: hidden;
}
/* GRID (adds high-frequency structure) */
.sh-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.06) 1px, transparent 1px);
background-size: 24px 24px;
opacity: 0.55;
}
/* BEAMS (hard readable structure) */
.sh-beam {
position: absolute;
width: 520px;
height: 44px;
border-radius: 999px;
animation: beamMove 7s ease-in-out infinite;
opacity: 0.95;
}
.beam-1 {
background: linear-gradient(
90deg,
transparent,
rgba(120,190,255,1),
rgba(120,190,255,0.45),
transparent
);
}
.beam-2 {
background: linear-gradient(
90deg,
transparent,
rgba(255,120,200,1),
rgba(255,120,200,0.4),
transparent
);
}
/* NODES (sharp light points) */
.sh-node {
position: absolute;
width: 16px;
height: 16px;
border-radius: 50%;
animation: nodeFloat 6s ease-in-out infinite;
}
.node-1 {
top: 35%;
left: 30%;
background: #ffffff;
/* stronger temporal visibility */
box-shadow:
0 0 28px rgba(120,180,255,1),
0 0 70px rgba(120,180,255,0.6);
}
.node-2 {
bottom: 30%;
right: 28%;
animation-delay: -3s;
background: #ffffff;
box-shadow:
0 0 28px rgba(255,120,190,1),
0 0 70px rgba(255,120,190,0.55);
}
/* -------------------------------- */
/* PANEL (ONLY DIFFERENCE = BLUR) */
/* -------------------------------- */
.sh-panel {
position: relative;
z-index: 2;
height: 100%;
background: rgba(10, 12, 18, 0.08);
border: 1px solid rgba(255,255,255,0.12);
}
.sh-panel.with-blur {
backdrop-filter: blur(10px) saturate(180%);
-webkit-backdrop-filter: blur(10px) saturate(180%);
}
/* lighting layer */
.sh-panel::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(255,255,255,0.16),
rgba(255,255,255,0.04) 20%,
transparent 45%
);
pointer-events: none;
}
/* ensure content sits above bg */
.sh-inbox-header,
.sh-email-list {
position: relative;
z-index: 2;
}
/* -------------------------------- */
/* CONTENT */
/* -------------------------------- */
.sh-inbox-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18px 22px;
border-bottom: 1px solid rgba(255,255,255,0.06);
}
.sh-app-name {
font-size: 14px;
font-weight: 600;
color: rgba(255,255,255,0.95);
}
.sh-badge {
font-size: 11px;
padding: 4px 8px;
border-radius: 999px;
color: rgba(180,255,180,0.95);
background: rgba(120,255,120,0.08);
border: 1px solid rgba(120,255,120,0.14);
}
.sh-email-list {
padding: 10px 0;
}
.sh-email-item {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 22px;
}
.sh-email-item:hover {
background: rgba(255,255,255,0.04);
}
.sh-avatar {
width: 34px;
height: 34px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 12px;
background: linear-gradient(135deg, #7a5cff, #5a8cff);
color: white;
}
.sh-avatar.sh-read {
background: rgba(255,255,255,0.08);
color: rgba(255,255,255,0.5);
}
.sh-email-from {
font-size: 13px;
font-weight: 600;
color: rgba(255,255,255,0.95);
}
.sh-email-subject {
font-size: 12px;
color: rgba(255,255,255,0.65);
}
.sh-time {
margin-left: auto;
font-size: 11px;
color: rgba(255,255,255,0.5);
}
/* -------------------------------- */
/* MOBILE */
/* -------------------------------- */
@media (max-width: 820px) {
.sh-comparison {
grid-template-columns: 1fr;
}
}Did you see that the animation runs on the background elements inside .sh-bg-field (the moving beams and particles behind the panel) not on .sh-panel itself?
There’s a reason for that. Animating backdrop-filter values directly is expensive and tends to introduce jank on most devices.
Tip: Remember to keep the glass surface static and animate whatever sits behind it!
10. shadcn-glass-ui: Building a Token-Based Glass System
The shadcn-glass-ui library proves glassmorphism can be systematized. Over 50 components, ranging from buttons and inputs to modals, navbars, and cards, all built on consistent glass tokens.
The lesson isn’t “use the library” (though you can). It’s that glassmorphism works at scale when you define the tokens once and apply them consistently.
If you’re building more than two or three glass components in the same project, this pattern saves a lot of chasing rgba values across your stylesheet.
Codepen: Glassmorphism: shadcn-glass-ui
<div class="token-demo">
<div class="glass-card">
<h3 class="card-title">Token-Based Glass</h3>
<p class="card-subtitle">Define once, apply consistently.</p>
<div class="glass-input-group">
<input class="glass-input" type="text" placeholder="Your email">
<button class="glass-btn primary">Subscribe</button>
</div>
<div class="glass-tag-row">
<span class="glass-tag">Design</span>
<span class="glass-tag">CSS</span>
<span class="glass-tag active">Systems</span>
</div>
</div>
</div>/* Glass design tokens — one place to update the whole system */
:root {
--glass-bg: rgba(255, 255, 255, 0.12);
--glass-bg-hover: rgba(255, 255, 255, 0.18);
--glass-bg-active: rgba(255, 255, 255, 0.22);
--glass-border: rgba(255, 255, 255, 0.2);
--glass-blur: 20px;
--glass-saturate: 160%;
--glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
--glass-radius: 16px;
--glass-text: rgba(255, 255, 255, 0.9);
--glass-text-muted: rgba(255, 255, 255, 0.55);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
.token-demo {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f64f59 100%);
display: flex;
align-items: center;
justify-content: center;
padding: 40px 20px;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.glass-card {
background: var(--glass-bg);
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
border: 1px solid var(--glass-border);
border-radius: var(--glass-radius);
box-shadow: var(--glass-shadow);
padding: 32px;
width: 380px;
display: flex;
flex-direction: column;
gap: 20px;
}
.card-title {
color: var(--glass-text);
font-size: 18px;
font-weight: 600;
}
.card-subtitle {
color: var(--glass-text-muted);
font-size: 14px;
line-height: 1.5;
margin-top: -10px;
}
.glass-input-group {
display: flex;
gap: 8px;
}
.glass-input {
flex: 1;
background: rgba(255, 255, 255, 0.08);
border: 1px solid var(--glass-border);
border-radius: 10px;
padding: 10px 14px;
color: var(--glass-text);
font-size: 14px;
outline: none;
transition: border-color 0.15s, background 0.15s;
}
.glass-input::placeholder {
color: var(--glass-text-muted);
}
.glass-input:focus {
border-color: rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.12);
}
.glass-btn {
padding: 10px 18px;
border-radius: 10px;
border: 1px solid var(--glass-border);
color: var(--glass-text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
background: var(--glass-bg);
white-space: nowrap;
}
.glass-btn.primary {
background: var(--glass-bg-active);
border-color: rgba(255, 255, 255, 0.35);
}
.glass-btn:hover {
background: var(--glass-bg-hover);
}
.glass-tag-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.glass-tag {
padding: 5px 12px;
border-radius: 20px;
font-size: 12px;
color: var(--glass-text-muted);
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.12);
cursor: pointer;
transition: all 0.15s;
}
.glass-tag.active {
color: var(--glass-text);
background: var(--glass-bg-active);
border-color: var(--glass-border);
}
.glass-tag:hover:not(.active) {
background: var(--glass-bg-hover);
color: var(--glass-text);
}Note 👀
Thebackdrop-filtervalues are split into separate--glass-blurand--glass-saturatetokens so you can adjust blur and saturation independently without rewriting the whole property. Safari handles custom properties insidebackdrop-filtercorrectly in recent versions, but if you’re targeting older Safari, you can always inline the values directly on the element as a fallback.
The Accessibility Side of Glass: What I Call the “Glass Contract”
Okay, let’s now take a look at the section where a lot of glassmorphism implementations quietly fail. Everything looks gorgeous in the mockup, you ship it, and then someone runs an accessibility audit, and it’s a mess.
I’ll be honest, this is really hard.
Testing contrast on a glass surface isn’t the same as testing a solid button because you’re not checking one color value against another. In this case, you’re checking against a background that moves.
If you thought theme selection (light/dark) was a pain, you’re in for a shocker.
What WCAG Actually Requires
WCAG 2.2 Level AA requires a contrast ratio of at least 4.5:1 for body text and 3:1 for large text and UI components like button borders and input outlines.
With glass, the problem is that your contrast ratio isn’t one number. It’s different depending on what’s behind the glass at any point.
Your white nav label might read perfectly fine over a dark section of the background image and then become nearly invisible two seconds later when the user scrolls to a lighter area.
Passing the contrast check in the best-case scenario and calling it done isn’t actually passing 💁♀️
My fix for this is what I call the tint rule: don’t put text on a surface that’s just blur plus near-zero opacity (aka “raw glass”). Always add a solid semi-transparent tint behind the text. Dark tint for light text, light tint for dark text—something in the 10–45% opacity range depending on your design.
Tip: Text shadows are your second line of defense. A subtle
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4)on light text over glass improves legibility without changing the visual design.
The comparison below shows both approaches against a background that shifts from dark to light. Raw glass on the left, tinted glass on the right. Watch what happens to the text as your eye moves across the gradient.

<div class="contrast-demo">
<!-- LEFT: Raw glass — contrast fails over the light zone -->
<div class="contrast-col">
<p class="contrast-label">Raw Glass — Unpredictable Contrast</p>
<div class="contrast-bg">
<div class="glass-raw">
<p class="glass-heading">Card Title</p>
<p class="glass-body">This text becomes hard to read as the background shifts from dark to light behind the glass surface.</p>
<button class="glass-cta-raw">Action</button>
</div>
</div>
</div>
<!-- RIGHT: Tinted glass — contrast is reliable across the whole gradient -->
<div class="contrast-col">
<p class="contrast-label">Tinted Glass — Reliable Contrast</p>
<div class="contrast-bg">
<div class="glass-tinted">
<p class="glass-heading">Card Title</p>
<p class="glass-body">The dark tint gives text a consistent contrast floor regardless of what the background is doing underneath.</p>
<button class="glass-cta-tinted">Action</button>
</div>
</div>
</div>
</div>* { box-sizing: border-box; margin: 0; padding: 0; }
.contrast-demo {
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 1fr;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.contrast-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 40px 20px;
}
.contrast-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
color: rgba(0, 0, 0, 0.45);
text-align: center;
max-width: 240px;
}
/* Background shifts from near-black to near-white — simulates a variable real-world background */
.contrast-bg {
width: 100%;
max-width: 380px;
min-height: 500px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg, #0d0d1a 0%, #4a4a6a 40%, #c8c8d8 70%, #f0f0f5 100%);
padding: 20px;
overflow: hidden;
}
/* RAW GLASS — only blur, almost no tint. Text fails over the light zone. */
.glass-raw {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 24px;
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
}
/* TINTED GLASS — dark tint at 45% opacity gives a consistent contrast floor */
.glass-tinted {
background: rgba(15, 15, 30, 0.45);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 24px;
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
}
.glass-heading {
font-size: 16px;
font-weight: 700;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
}
.glass-body {
font-size: 13px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.85);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.glass-cta-raw {
padding: 8px 18px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.25);
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
font-weight: 600;
cursor: pointer;
width: fit-content;
}
.glass-cta-tinted {
padding: 8px 18px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.15);
color: rgba(255, 255, 255, 0.95);
font-size: 13px;
font-weight: 600;
cursor: pointer;
width: fit-content;
}On the left panel, as the background shifts from dark toward light, the text fades with it since there’s no contrast floor.
On the right, the dark tint at 45% opacity means the background shift barely registers on the text. Same blur on both. The tint is the only difference.
Dark Mode Glass Is Harder Than It Looks
One thing you run into pretty quickly with glassmorphism is that the same panel can look great in light mode and completely fall apart in dark mode.
The usual culprit is opacity. A value like rgba(255, 255, 255, 0.08) or 0.12 often looks clean on lighter surfaces, but against a near-black background, the panel loses definition. The blur is technically still there, but visually, the glass starts blending into the scene instead of separating from it.
If you try to solve by raising the opacity, then you risk the panel looking like frosted plastic instead of glass 😬
What tends to work better is balancing a few smaller adjustments together:
- slightly increasing the surface opacity
- strengthening the edge lighting and border contrast
- adding subtle internal highlights
- keeping enough transparency for the background to still feel present underneath
The goal is readability and separation without losing the translucent quality that makes the effect feel like glass in the first place.
Both panels below use the same background and the same basic blur setup. The difference is how the glass itself is tuned.
<div class="darkmode-demo">
<div class="darkmode-col">
<p class="demo-label">
Weak dark-mode glass
</p>
<div class="scene">
<div class="glass weak-glass">
<p>
Low-opacity glass on dark surfaces tends to lose definition.
The blur exists, but the panel barely separates from the scene.
</p>
</div>
</div>
</div>
<div class="darkmode-col">
<p class="demo-label">
Proper dark-mode glass
</p>
<div class="scene">
<div class="glass strong-glass">
<p>
Stronger edge lighting, controlled opacity, and layered highlights
preserve the glass effect without making the panel feel opaque.
</p>
</div>
</div>
</div>
</div>* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #0b0d12;
font-family: Inter, sans-serif;
color: white;
}
/* -------------------------------- */
/* LAYOUT */
/* -------------------------------- */
.darkmode-demo {
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 1fr;
background: #0b0d12;
}
.darkmode-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 18px;
padding: 48px;
}
.demo-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
color: rgba(255,255,255,0.42);
text-align: center;
}
/* -------------------------------- */
/* SCENE */
/* -------------------------------- */
.scene {
position: relative;
width: 100%;
max-width: 420px;
height: 460px;
overflow: hidden;
border-radius: 32px;
background:
radial-gradient(
circle at top left,
rgba(110,140,255,0.22),
transparent 30%
),
radial-gradient(
circle at bottom right,
rgba(255,120,180,0.14),
transparent 38%
),
linear-gradient(
180deg,
#171a22 0%,
#0b0d12 100%
);
}
/* environmental light */
.scene::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(
135deg,
rgba(255,255,255,0.06),
transparent 45%
);
pointer-events: none;
}
/* subtle glow depth */
.scene::after {
content: "";
position: absolute;
width: 240px;
height: 240px;
bottom: -80px;
right: -60px;
border-radius: 50%;
background: rgba(90,140,255,0.12);
filter: blur(70px);
pointer-events: none;
}
/* -------------------------------- */
/* GLASS BASE */
/* -------------------------------- */
.glass {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 82%;
padding: 30px 28px;
border-radius: 28px;
backdrop-filter: blur(14px) saturate(145%);
-webkit-backdrop-filter: blur(14px) saturate(145%);
overflow: hidden;
}
.glass p {
position: relative;
z-index: 3;
font-size: 14px;
line-height: 1.7;
color: rgba(255,255,255,0.82);
}
/* internal atmospheric lighting */
.glass::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background:
linear-gradient(
180deg,
rgba(255,255,255,0.18) 0%,
rgba(255,255,255,0.07) 20%,
rgba(255,255,255,0.02) 55%,
transparent 100%
);
pointer-events: none;
}
/* restrained reflective sheen */
.glass::after {
content: "";
position: absolute;
top: -20%;
left: -10%;
width: 120%;
height: 55%;
background:
linear-gradient(
135deg,
rgba(255,255,255,0.07),
rgba(255,255,255,0.015) 42%,
transparent 60%
);
transform: rotate(-6deg);
pointer-events: none;
}
/* -------------------------------- */
/* WEAK VERSION */
/* -------------------------------- */
.weak-glass {
background: rgba(255,255,255,0.06);
border-top: 1px solid rgba(255,255,255,0.10);
border-left: 1px solid rgba(255,255,255,0.08);
border-right: 1px solid rgba(255,255,255,0.05);
border-bottom: 1px solid rgba(255,255,255,0.04);
box-shadow:
0 12px 24px rgba(0,0,0,0.16);
}
/* weaker definition */
.weak-glass::before {
opacity: 0.52;
}
/* -------------------------------- */
/* PROPER DARK MODE GLASS */
/* -------------------------------- */
.strong-glass {
background: rgba(255,255,255,0.14);
border-top: 1px solid rgba(255,255,255,0.24);
border-left: 1px solid rgba(255,255,255,0.14);
border-right: 1px solid rgba(255,255,255,0.08);
border-bottom: 1px solid rgba(255,255,255,0.05);
box-shadow:
inset 0 1px rgba(255,255,255,0.10),
0 22px 44px rgba(0,0,0,0.28);
}
/* stronger lighting and readability */
.strong-glass::before {
opacity: 1;
}
.strong-glass p {
color: rgba(255,255,255,0.9);
}
Left panel:
- Glass layer is too weak for the environment behind it
- Panel blends into the background, losing visual separation
- Blur exists, but the surface itself lacks enough definition to feel intentional
Right panel:
- Opacity is increased slightly, not aggressively
- Stronger edge lighting and contrast help define the panel shape
- Internal highlights keep the surface feeling reflective and layered instead of flat
- Background still comes through, so the material reads as glass instead of an opaque card
Sensory and Motion Safety
Some users experience eye strain, headaches, or dizziness from heavy blur and animated translucent surfaces. It’s a bigger chunk of your user base than you assume; this isn’t a rare edge case.
There are two OS-level preferences that users can enable as a signal:
prefers-reduced-transparencyremoves blur and transparency effects (on macOS it’s under Accessibility > Display > Reduce Transparency)prefers-reduced-motionremoves animations and transitions
Both have been around for a while, but glass implementations rarely account for them.
When prefers-reduced-transparency is active, your glass surface should fall back to a solid or near-solid background. When prefers-reduced-motion is active, animations and transitions on your glass elements should stop entirely.
Note: Neither of these is optional. In some regions, they’re becoming a legal requirement under accessibility law, not just a recommendation.
The comparison below shows the same glass card in two states: as it renders normally on the left, and as it should render for a user who has both preferences enabled on the right.
<div class="motion-demo">
<!-- LEFT: Default rendering — glass + animation, no media query consideration -->
<div class="motion-col">
<p class="motion-label">Default — No Preference Handling</p>
<div class="motion-scene default-scene">
<div class="default-glow"></div>
<div class="default-panel">
<p class="panel-title">Notifications</p>
<p class="panel-body">3 new messages from your team. Glass surface animates with the background glow.</p>
<button class="panel-btn">View All</button>
</div>
</div>
</div>
<!-- RIGHT: Reduced preferences applied — solid fallback, no animation -->
<div class="motion-col">
<p class="motion-label">With Reduced Preferences — Solid Fallback</p>
<div class="motion-scene reduced-scene">
<!-- No animated glow, solid panel -->
<div class="reduced-panel">
<p class="panel-title">Notifications</p>
<p class="panel-body">3 new messages from your team. Solid surface, no animation, full contrast.</p>
<button class="panel-btn">View All</button>
</div>
</div>
</div>
</div>* { box-sizing: border-box; margin: 0; padding: 0; }
@keyframes glow-drift {
0%, 100% { transform: translate(0, 0); opacity: 0.8; }
50% { transform: translate(20px, -15px); opacity: 1; }
}
.motion-demo {
min-height: 100vh;
display: grid;
grid-template-columns: 1fr 1fr;
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
}
.motion-col {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 40px 20px;
}
.motion-label {
font-size: 11px;
font-weight: 600;
letter-spacing: 0.8px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.4);
text-align: center;
max-width: 220px;
}
.motion-scene {
width: 100%;
max-width: 280px;
min-height: 280px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
/* LEFT: animated dark background */
.default-scene {
background: #06060f;
}
/* Animated glow behind the glass panel */
.default-glow {
position: absolute;
width: 300px;
height: 300px;
border-radius: 50%;
background: radial-gradient(circle,
rgba(100, 60, 220, 0.8) 0%,
rgba(200, 60, 150, 0.5) 50%,
transparent 75%
);
animation: glow-drift 4s ease-in-out infinite;
filter: blur(30px);
}
/* Glass panel over the animation */
.default-panel {
position: relative;
z-index: 1;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(24px) saturate(150%);
-webkit-backdrop-filter: blur(24px) saturate(150%);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
padding: 24px;
width: 88%;
transition: background 0.3s ease;
}
/* RIGHT: no animation, solid fallback — what reduced-preference users see */
.reduced-scene {
background: #1a1a2e; /* solid dark, no glow animation */
}
.reduced-panel {
background: rgba(30, 30, 50, 0.92); /* near-solid, no backdrop-filter */
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 16px;
padding: 24px;
width: 88%;
}
.panel-title {
font-size: 15px;
font-weight: 700;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 10px;
}
.panel-body {
font-size: 13px;
line-height: 1.5;
color: rgba(255, 255, 255, 0.75);
margin-bottom: 16px;
}
.panel-btn {
padding: 8px 18px;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.25);
background: rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
/*
These two queries are what make the right panel the production standard.
Copy this pattern into every glass component you ship.
*/
/* When the user has Reduce Transparency turned on at the OS level */
@media (prefers-reduced-transparency: reduce) {
.default-panel {
background: rgba(30, 30, 50, 0.92);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
/* When the user has Reduce Motion turned on at the OS level */
@media (prefers-reduced-motion: reduce) {
.default-glow {
animation: none;
}
.default-panel {
transition: none;
}
}- Left panel: animated glow, glass surface, the full effect.
- Right panel: solid background, no animation, near-full-opacity surface—exactly what the two media queries produce for users who’ve enabled those preferences.
The right panel is what you’re building toward. The left panel is the progressive enhancement on top of it.
Tip 📍
The media queries in this example are the same two that should be in every glass component you ship. Pull them out, swap.default-paneland.default-glowfor your own class names, and you’re covered.
Good vs. Bad: Two Implementations That Tell the Whole Story
What Apple Gets Right: The Fallback
Apple’s Liquid Glass has a built-in escape hatch: the “Reduce Transparency” toggle in Accessibility settings. When it’s on, all the glass surfaces across iOS and macOS become solid colors. No blur, no guessing about contrast, no visual noise. Flat, high-contrast panels that just work.
That’s the right approach.
Build the solid fallback first, and add the glass on top as a progressive enhancement.
The effect is something users get if their device supports it and their preferences don’t exclude it, not something imposed on everyone.
What Goes Wrong: The Figma Problem
The most common failure I see isn’t someone trying to be lazy. It’s actually Figma’s fault, or at least, good design tools deserve some of the blame.
Figma renders glass beautifully at any blur setting, over any background. Everything looks readable. A 4px blur looks gorgeous in a mockup because you can just barely see the background, and it feels like depth.
Put that same panel in production, and the background elements are still identifiable. Your nav label is sitting on top of text from the hero section below it. The two layers start competing instead of layering.
That’s the low-blur problem. At 20–40px, the background blurs into pure atmosphere so you know something’s back there, but it doesn’t register as content. At 4–6px, it’s still readable content fighting your foreground.
Test in the browser with real content behind your glass panels, over images you don’t control, at different scroll positions. If the text ever looks ambiguous, the blur isn’t doing its job.
Explore: Stop Missing Out On These Really Helpful Design Features In Chrome DevTools
What Users and Developers Actually Think
The reception to glassmorphism, and Apple’s Liquid Glass specifically, isn’t a clean “everyone loves it” or “everyone hates it.”
It’s messier, and that mess reflects real tradeoffs worth considering.
The Developer Frustration Is Real
Here’s my unofficial dev take: backdrop-filter is expensive. Every glass element triggers separate GPU blur calculations, and those stack up.
Three or four elements? Fine on modern hardware.
Ten or fifteen? You’ll feel it on mid-range Android.
And animating backdrop-filter itself, like transitioning blur(0px) to blur(20px), causes jank on most devices. Don’t do it. Animate opacity or use a CSS transform instead.
Getting glass to look right across light mode, dark mode, different backgrounds, and different screen brightness levels is also not a set-it-and-forget-it situation. You’re chasing a moving target.
I didn’t fully appreciate that before trying it 😅
The Design Community Is Split on It
Some designers see glassmorphism as a sign of craft, the kind of finish that separates a polished product from a generic template. When it’s done well, it communicates quality that flat design struggles to match.
Others see it as the new neumorphism: impressive in portfolios, underperforms in production.
There’s real evidence for that concern since Apple’s Liquid Glass has attracted more criticism on macOS Tahoe than on iOS, with the style reportedly not translating as cleanly to larger screens and desktop interactions.
Apple is reportedly working on refinements for the next version. Even with their level of resources, glassmorphism at scale requires ongoing adjustment. That in itself should tell you something 🙂↕️
Users Are Somewhere in the Middle
The consistent thread in user feedback on Liquid Glass is something like: “It looks interesting, but it’s not quite there yet.”
The aesthetics are okay. The usability details, like which surfaces get glass, at what blur, with what tint, with what fallbacks, are still being figured out across the industry.
That’s a fair place to land on it, I think.
It’s a Wrap
Glassmorphism is one of those things where the more you understand it, the better your decisions get.
Used with intention—right surfaces, right blur, right contrast, right fallbacks—it definitely elevates an interface.
Used carelessly, it’s just a blurry mess that looks great in Figma and fails in production.
Apple’s commitment to Liquid Glass means this aesthetic is baked into the design vocabulary for the foreseeable future. The industry will keep refining it.
That means it’s worth understanding now rather than trying to reverse-engineer it after it’s everywhere.
Grab the CodePens from the links and play around. If you build something fun with the token system from example 10, or find a creative way to use depth stabilization, I’d love to see it in the comments 🙌
‘Till next time, friends ✌️