We shipped a login screen on iOS 26 with two buttons styled using the new .glassEffect() API. Signup worked. Login didn't. No crash, no warning, no error -- just... nothing happened when you tapped it.
It took longer to find than I'd like to admit.
The Setup
Two buttons stacked vertically, 26pt apart. Standard VStack layout:
VStack(spacing: 26) {
Spacer()
Button {
store.send(.signupButtonTapped)
} label: {
Text("Sign Up")
.padding()
.frame(width: buttonWidth, height: 55)
}
.glassEffect(.clear.interactive())
.buttonStyle(.plain)
Button {
store.send(.loginButtonTapped)
} label: {
Text("Log In")
.padding()
.frame(width: buttonWidth, height: 50)
}
.glassEffect(.clear.interactive())
.buttonStyle(.plain)
}
Signup button: works fine. Login button: completely dead. Identical code, identical modifiers, only position differs.
The Cause
When you call .glassEffect(.clear.interactive()) without specifying a shape, the glass platter defaults to a rectangle. iOS 26's Liquid Glass system automatically merges adjacent glass surfaces that are close enough together into a single interactive group.
Once merged, the .interactive() gesture handler routes taps to the first view in the hierarchy -- in this case, the signup button. The login button's taps are silently consumed by the merged glass group and never reach the button's action.
The merging is by design -- Apple wants glass elements to feel like one fluid surface. But the gesture routing side effect is brutal to debug because there's zero feedback that anything went wrong.
The Fix
Specify an explicit shape with the in: parameter:
// Before (broken)
.glassEffect(.clear.interactive())
// After (works)
.glassEffect(.clear.interactive(), in: .capsule)
That's it. One parameter. Giving each button a distinct glass shape prevents the automatic merge, so each button gets its own gesture handler.
A Reusable Modifier
If you're wrapping glass effects in a ViewModifier for backward compatibility (which you should), make sure to include the shape:
struct GlassButtonModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(iOS 26.0, *) {
content.glassEffect(.clear.interactive(), in: .capsule)
} else {
content
.background(.ultraThinMaterial, in: Capsule())
}
}
}
When to Watch Out
This will bite you whenever:
- Two or more
.glassEffect()views are adjacent (VStack, HStack, ZStack) - You're using
.interactive()for the press/bounce feedback - You haven't specified a shape with
in:
It's especially dangerous because the first button always works -- so you might not catch it until a user reports that a specific button is dead.
TL;DR
| Merges? | Taps work? | |
|---|---|---|
.glassEffect(.clear.interactive()) |
Yes (rectangle, auto-merges) | Only first button |
.glassEffect(.clear.interactive(), in: .capsule) |
No (distinct shapes) | All buttons |
Always pass a shape to .glassEffect() when using .interactive() on adjacent views. Future you will thank present you.
Sources:
This article was originally published by DEV Community and written by Daksh Gargas.
Read original article on DEV Community