Fix: Liquid Glass Sheet Color Mismatch Between Views and .borderedProminent Buttons in SwiftUI

Fix: Liquid Glass Sheet Color Mismatch Between Views and .borderedProminent Buttons in SwiftUI

Written by

in

Quick answer: In iOS 26+ Liquid Glass sheets, system colors like Color.blue adapt to the glass vibrancy while .borderedProminent buttons use the unadjusted tint. Fix it by adding .opacity(1) to your color fills, or create a custom ButtonStyle that matches the adapted color.

Why this happens

On iOS 26 and later, partial-height sheets (.medium or .fraction detents) render with the new Liquid Glass material. When you use a semantic system color such as Color.blue inside that sheet, SwiftUI resolves the color just before drawing and applies the glass’s vibrancy adaptation. The result is a shifted hue β€” lighter and more cyan in dark mode β€” that matches the surrounding translucency.

However, the built-in .borderedProminent button style does not receive this adaptation. It always paints its background with the raw, unmodified system tint (exactly UIColor.systemBlue). Because the two rendering paths diverge, an icon tile filled with Color.blue and a primary button tinted with the same Color.blue visibly disagree.

The discrepancy only appears while the sheet is in a glass detent. At .large (full-screen) or when you force a solid .presentationBackground, both elements render the identical base color. The issue is reproducible on iOS 26.5 and iOS 27 simulators with Xcode 27.

How to fix it

1. Make the fill color opaque (quickest fix)

The simplest way to align the two elements is to stop the glass from adapting your fill color. Applying .opacity(1) to the Color forces SwiftUI to resolve it as a fully opaque value, bypassing the vibrancy shift.

Image(systemName: "star.fill")
    .font(.title)
    .foregroundStyle(.white)
    .frame(width: 60, height: 60)
    .background(
        Color.blue.opacity(1),   // πŸ‘ˆ opaque – no glass adaptation
        in: .rect(cornerRadius: 14)
    )

The button keeps its native .borderedProminent behavior (press state, accessibility, dynamic type) while the icon background now matches the button’s exact tint.

Note: This also works for Text foregrounds and shape fills: Text("Label").foregroundStyle(Color.blue.opacity(1)) or RoundedRectangle(...).fill(Color.blue.opacity(1)).

2. Build a custom ButtonStyle that uses the adapted color (full parity)

If you prefer the button to follow the same glass-adapted hue as the rest of your content β€” so everything shifts together when the sheet’s backdrop changes β€” replicate .borderedProminent in a custom ButtonStyle. The style reads the environment’s controlSize and tint, applies the same padding metrics, and adds a pressed-state highlight.

import SwiftUI

struct GlassProminentButtonStyle: ButtonStyle {
    @Environment(\ .controlSize) private var controlSize

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .font(.headline)                    // matches .controlSize(.large)
            .padding(.horizontal, controlSize == .large ? 20 : 12)
            .padding(.vertical, controlSize == .large ? 15 : 7)
            .frame(maxWidth: .infinity)
            .foregroundStyle(.white)
            .background(
                configuration.isPressed
                    ? AnyShapeStyle(.tint.opacity(0.7))  // lighter on press
                    : AnyShapeStyle(.tint),              // adapted tint
                in: .rect(cornerRadius: controlSize == .large ? 16 : 10)
            )
            .contentShape(.rect(cornerRadius: controlSize == .large ? 16 : 10))
            .animation(.easeOut(duration: 0.1), value: configuration.isPressed)
    }
}

Usage:

Button("Action") { }
    .buttonStyle(GlassProminentButtonStyle())
    .controlSize(.large)
    .tint(Color.blue)   // now adapts like the icon background

Because the background uses .tint (which resolves to the glass-adapted Color.blue), the button and the icon tile stay visually identical in every lighting condition.

3. Use a fixed UIColor-based color (escape adaptation entirely)

If you never want adaptation β€” for brand colors that must stay pixel-perfect β€” construct the color from UIColor or literal RGBA values. These colors are not context-dependent and render identically everywhere.

let brandBlue = Color(uiColor: UIColor(red: 0/255, green: 145/255, blue: 255/255, alpha: 1))
// or
let brandBlue = Color(red: 0/255, green: 145/255, blue: 255/255)

// then
.background(brandBlue, in: .rect(cornerRadius: 14))
.tint(brandBlue)

This approach sacrifices the automatic light/dark and glass adaptations, so use it only for immutable brand palettes.

How to prevent it

  • Audit color usage in glass sheets β€” any Color.blue, .red, .green, etc. used in fills or foregrounds will adapt; .borderedProminent will not.
  • Pick one strategy per screen: either make all fills opaque with .opacity(1), or adopt a custom button style that consumes the adapted tint.
  • Test at every detent β€” the mismatch only appears at .medium/.fraction; .large and solid backgrounds hide it.
  • Prefer semantic colors for chrome, fixed colors for brand β€” system colors are designed to adapt; brand colors usually aren’t.

FAQ

Does this affect all system colors?

No. Pure white (.white) and pure black (.black) do not shift because they have no chromatic component to adapt. Every other semantic system color (.blue, .red, .mint, etc.) exhibits the glass adaptation in a Liquid Glass sheet.

Can I detect whether the sheet is currently using Liquid Glass?

There is no public API to query the sheet’s material at runtime. The adaptation is driven by the presentation detent: .medium and fractional detents use glass; .large and custom solid backgrounds do not. Design for the glass case and verify at .large as a sanity check.

Will Apple fix this in a future SDK?

The behavior stems from a deliberate design choice: system colors are documented as “context-dependent” and resolve at render time. The .borderedProminent style intentionally uses the raw tint for consistency across non-glass contexts. Expect the divergence to remain; adopt one of the workarounds above for visual parity.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.