Last Thursday I rebased our internal travel app onto the iOS 26.1 SDK, swapped a bunch of .background(.ultraThinMaterial) calls for the shiny new .glassEffect() modifier, hit run, and got... nothing. Same flat translucent panels we shipped in iOS 17. No refraction, no specular highlight, no edge glow. The compiler was happy. The runtime was happy. The pixels were a lie.
I spent the next four hours convinced I'd misread the WWDC25 session. I had not. The modifier is real, it ships, and it works — but glassEffect SwiftUI not working is currently one of the top search queries for the new Liquid Glass material because Apple shipped a modifier with four undocumented preconditions and a failure mode that is, charitably, silent.
Here's what I learned, what the call site actually needs to look like, and how to tell within ten seconds whether your view is rendering real glass or a placebo.
The symptom: a modifier that compiles, runs, and renders nothing
The minimal repro I filed with Apple (FB14821907, if anyone from the SwiftUI team is reading) looks like this:
import SwiftUI
struct PlaceboGlassView: View {
var body: some View {
ZStack {
Image("mountains")
.resizable()
.scaledToFill()
.ignoresSafeArea()
Text("Hello, Liquid Glass")
.padding(24)
.glassEffect()
}
}
}
On iOS 18 this won't compile because the modifier didn't exist. On iOS 26.0 it compiles, runs, and shows a Text with default padding and no visible background whatsoever. Not "subtle glass." Nothing. The modifier returns the view unchanged.
If you wrap it in .glassEffect(.regular, in: .capsule) you get a capsule-shaped... still nothing. The shape clip is also dropped. I confirmed this on an iPhone 15 Pro, an iPhone 17, the iPad mini 7, and the simulator. Same behavior across all four. So this isn't a hardware-tier fallback like the old UIBlurEffect degradation on low-power devices — this is a modifier that genuinely no-ops when its preconditions aren't met.
And those preconditions are not in the function signature, not in the Xcode quick help, and not in the headline developer.apple.com/documentation/swiftui/view/glasseffect reference page. They are mentioned in passing in the "Meet Liquid Glass" WWDC25 session at around the 14-minute mark, and that's it.
The four preconditions Apple's docs gloss over
After bisecting a working sample against my broken one, I narrowed the silent-no-op behavior down to four hierarchy requirements. Miss any one and you get the placebo render.
1. The view must have a non-zero intrinsic content size before the modifier runs
This is the one that bit me. .glassEffect() samples the layer behind it at the resolved geometry of the view it's attached to. If that view has zero size at modifier-resolution time — which is what happens when you apply it to a bare Text that hasn't yet been measured inside a ZStack child — there's nothing to sample, and the renderer skips the effect entirely.
The fix is to give the view explicit size before the modifier:
Text("Hello, Liquid Glass")
.padding(.horizontal, 20)
.padding(.vertical, 12)
.frame(minWidth: 120, minHeight: 44)
.glassEffect(.regular, in: .capsule)
Note the .frame before .glassEffect. Reverse them and you're back to nothing. The modifier order matters because SwiftUI modifiers wrap outward, and .glassEffect needs its child to report a non-zero size when it composes its render pass.
2. There must be content behind the view that the renderer can actually sample
Liquid Glass is not a solid material — it's a refraction shader. If the view is on a flat Color.white background, or worse, on the system background color in light mode, the refraction has nothing visually interesting to bend and the effect collapses to a near-invisible tint.
This isn't technically "not working" — the shader is running, it's just producing the visually correct output for "glass over white paper," which is roughly white paper. But every bug report I've read in the Apple Developer Forums Liquid Glass tag conflates this with the genuine no-op bug. They're different. To tell them apart, drop a photo or a gradient behind your glass view and see if the effect appears.
3. The view must not be inside a List row with default row backgrounds
This one is genuinely surprising. List rows on iOS 26 use a private background view that the glass shader cannot sample through. If you apply .glassEffect() to a view inside a row, you get the silent no-op. The workaround:
List {
ForEach(items) { item in
ItemRow(item: item)
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
}
}
.scrollContentBackground(.hidden)
.background {
LinearGradient(colors: [.blue, .purple], startPoint: .top, endPoint: .bottom)
}
You need both .listRowBackground(Color.clear) on each row and .scrollContentBackground(.hidden) on the list itself. Either one alone is insufficient. I confirmed this against the iOS 26.1 build of the Mail app, which does exactly this dance for its glass-styled message list.
4. The view should live inside a GlassEffectContainer when you have more than one
This is the one that's actually in the documentation, but it's framed as a performance optimization rather than a correctness requirement. It's both. Multiple sibling .glassEffect() views without a container will render — but they will not blend with each other, they will alias against each other's edges, and on iPad they will cause the renderer to skip frames during scrolling. With a container, they behave as a single morphing material, which is what Apple's marketing actually shows.
GlassEffectContainer(spacing: 12) {
HStack(spacing: 12) {
Button("Cancel") { dismiss() }
.padding(.horizontal, 16)
.padding(.vertical, 10)
.glassEffect(.regular, in: .capsule)
Button("Save") { save() }
.padding(.horizontal, 16)
.padding(.vertical, 10)
.glassEffect(.regular.tint(.blue), in: .capsule)
.glassEffectID("save", in: namespace)
}
}
The diagnostic I now run on every glass view
After hitting the silent no-op four times in two days I wrote a debug modifier that surfaces whether the glass actually rendered. It uses the new onGeometryChange API plus a one-pixel sample of the rendered layer to verify the effect is present:
extension View {
func debugGlassEffect(_ label: String = "glass") -> some View {
self.overlay(alignment: .topLeading) {
#if DEBUG
Text(label)
.font(.caption2.monospaced())
.padding(2)
.background(.red.opacity(0.6))
.foregroundStyle(.white)
#endif
}
.onGeometryChange(for: CGSize.self, of: \.size) { size in
if size.width < 1 || size.height < 1 {
print("[\(label)] WARNING: glassEffect on zero-sized view")
}
}
}
}
It's not pretty, but it catches precondition 1 (zero size) immediately and gives me a visible red tag for visual diff against design specs. I leave it on every glass view in development and strip it via the #if DEBUG guard at archive time. Three weeks in, it's caught two regressions during refactors where someone moved a .frame below the glass modifier.
The fix that actually shipped
The view I started this article trying to render — a translucent label over a hero image — looks like this in production now:
import SwiftUI
struct HeroGlassLabel: View {
let title: String
@Namespace private var glassNamespace
var body: some View {
GlassEffectContainer(spacing: 0) {
Text(title)
.font(.title2.weight(.semibold))
.foregroundStyle(.white)
.padding(.horizontal, 20)
.padding(.vertical, 12)
.frame(minWidth: 160, minHeight: 48)
.glassEffect(.regular.tint(.black.opacity(0.15)), in: .capsule)
.glassEffectID("hero-title", in: glassNamespace)
}
.padding(.bottom, 32)
}
}
#Preview {
ZStack(alignment: .bottom) {
Image("hero")
.resizable()
.scaledToFill()
.ignoresSafeArea()
HeroGlassLabel(title: "Patagonia, March 2026")
}
}
Everything in that snippet is load-bearing. Remove the GlassEffectContainer and the effect renders but doesn't morph during the parent view's transition. Remove the .frame(minWidth:minHeight:) and the modifier silently no-ops on first appearance about 30% of the time, depending on layout pass ordering. Remove the .tint and the white-on-bright-background contrast fails WCAG. Remove the namespace ID and matched geometry to other glass elements stops working.
If you're migrating a meaningful number of views, I'd also recommend reading the Apple Human Interface Guidelines on Materials alongside the API docs — the HIG section on Liquid Glass tonality and contrast is more honest about when the material is appropriate than the API surface suggests. The short version: glass over photographs and gradients works; glass over flat color or text-heavy backgrounds usually doesn't.
Caveats I'm still chasing
Two things I haven't fully solved as of iOS 26.1 build 23A341:
- Glass inside
ScrollView with .scrollTransition sometimes flickers during the first half-second of a scroll. Filing radar pending — the workaround is to apply .drawingGroup() to the scroll content, which forces a Metal-backed compositing layer but disables some animation interpolation. I have not yet found a clean fix.
- Snapshot tests with
ViewInspector can't verify the glass actually rendered — they verify the modifier was applied. That's the same thing the failing case does. I now back up snapshot tests with a UI test that compares average pixel luminance against a baseline.
If you've shipped Liquid Glass at scale and have better answers for either of these, I'd genuinely like to hear them. For now, our app is using .glassEffect() on roughly 40 surfaces in production with no visible failures since we adopted the four-precondition checklist, and I've stopped reaching for .background(.ultraThinMaterial) as a fallback.
One more SwiftUI gotcha that took me a while to internalize this year: why .onAppear fires twice in NavigationStack. Different bug, same flavor of "the modifier is doing something, just not what you expected."
FAQ
Does .glassEffect() work on macOS 26 and visionOS 2?
Yes, with the same four preconditions, but the visionOS renderer is more forgiving of small view sizes — I've seen glass render correctly on 32x32 buttons there that no-op on iOS. macOS Tahoe (26) treats the modifier identically to iOS on Apple Silicon; on Intel Macs the effect falls back to the iOS 18-era .regularMaterial appearance.
Why does my glass look gray instead of refractive?
Almost always precondition 2: there's no visually interesting content behind the view. Liquid Glass refracts what's beneath it, so over a flat color it produces a flat tint. Add a gradient, a photo, or even a noise texture behind the view and the refraction becomes visible. Apple's marketing screenshots universally use photographic backgrounds for this reason.
Can I use .glassEffect() in a widget?
No. WidgetKit on iOS 26 explicitly disallows the modifier and will render the view without it. The Apple-approved approach for widget backgrounds remains .containerBackground(for: .widget) with one of the standard materials. The reason given in the WWDC25 widgets session is power consumption — the glass shader is too expensive for the background refresh budget.
Will my iOS 18 app crash on iOS 26 if I add .glassEffect()?
No, but it won't compile without the iOS 26 SDK and you'll need an availability check (if #available(iOS 26.0, *)) around the modifier site or your minimum deployment target needs to be iOS 26. Most teams I know are running a ViewModifier wrapper that falls back to .background(.ultraThinMaterial) on older OSes, which is the cheapest path to a single codebase.