DEV Community

Daksh Gargas
Daksh Gargas

Posted on

iOS 26 Liquid Glass Gotcha: Adjacent Buttons Silently Swallowing Taps

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)
}
Enter fullscreen mode Exit fullscreen mode

App's Screenshot

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)
Enter fullscreen mode Exit fullscreen mode

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())
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

Top comments (0)