7 minute read

Liquid Glass in SwiftUI: .glassEffect and “glass” buttons

What Liquid Glass is (and where .glassEffect fits)

This article is part of the “Liquid Glass” series. If you haven’t read the first post, start with Liquid Glass — Introduction to understand the material and its uses before diving into .glassEffect and buttons.

📦 All samples are available on GitHub

Liquid Glass is the system’s adaptive material for controls and navigation. In iOS 26 you’ll see it in bars, menus, sheets, sliders, toggles, and more.

Practical tip: .glassEffect rounds the surface into a capsule by default and reads much better once you give the view some padding—you’ll also notice a soft, mostly downward shadow that makes the pill feel slightly lifted.

In SwiftUI, that material arrives in two ways:

  • Automatically in system controls (toolbars, sliders, toggles, menus, etc.).

  • Opt-in for your custom views via the .glassEffect(…) modifier.

Key guideline: use standard components (app structures, toolbars, placements, controls) and remove custom backgrounds or darkening behind bars so you don’t interfere with the scroll edge effect..

.glassEffect: variants (visual language) and API “shape”

Official variants

  • regular (default): balanced and legible in most contexts.
  • clear: more transparent and visually rich; often needs background dimming to preserve contrast on small surfaces or over very vivid content.

Important: Apple talks about two material variants (regular and clear). If you need to turn the effect off, use the API option Glass.identity—that’s an implementation choice, not a third visual variant. Also, avoid mixing regular and clear within the same control group; it hurts hierarchy and consistency.

    ZStack {
        Rectangle()
            .fill(Color.red.gradient)

        // In liquid glass, we have three types to make with glassEffect
        VStack(spacing: 16) {
            Text("Hello, world!")
                .padding()
                .glassEffect(.regular.interactive()) // default

            HStack {
                Text("Hello, world!")
                    .padding()
                    .glassEffect(.clear) // Remove the tint color, pick up the background color, and increase the transparency of the element

                Text("Hello, world!")
                    .padding()
                    .glassEffect(.identity)
            }

            HStack(spacing: 24) {
                // The default liquid glass buttons are capsules by default, but with the parameter en we can tell it what geometric shape you want
                Text("Hello, world!")
                    .padding()
                    .glassEffect(.regular.tint(.blue).interactive(), in: .ellipse)

                Text("Hello, world!")
                    .padding()
                    .glassEffect(.regular.tint(.blue).interactive())
            }

            Text("Hello, world!")
                .padding()
                .glassEffect(.regular.interactive()) // default
        }
    }

📦 All samples are available on GitHub

Firma (conceptual) — API SwiftUI:

view.glassEffect(variant, in: Shape = Capsule(), interactive: Bool = false)

tint(…) is applied afterward as a View modifier (it’s not part of the signature). Use it only to convey meaning (primary action, state), not as decoration.

“Glass” button styles

SwiftUI includes button styles that bring Liquid Glass to any action. Use them when your buttons live in your content area. Buttons that appear on system surfaces (e.g., toolbars) already benefit from the host surface’s glass automatically.

  • .glass: translucent and “clear-like”; it reacts as you press and reflects the background.
  • .glassProminent: opaque (no background show-through) yet still reacts like glass; use it for the primary action.

When do buttons get Liquid Glass?

  • System surfaces (toolbars, search, etc.): Automatic. Don’t add styles just to “force” glass; remove custom backgrounds so edge effects can do their job.

  • Standalone buttons in your content: Opt in with .buttonStyle(.glass) or .buttonStyle(.glassProminent) (then .tint(…) only when it conveys meaning).

  • Custom controls (tags, bespoke views): Apply .glassEffect(...) yourself (and interactive: true if it’s tappable).

Toolbars in the new design: items are grouped automatically, monochrome by default (tint only when it conveys meaning), use ToolbarSpacer to create groups and badge(_:) for indicators. Remove custom backgrounds/dimming so the scroll edge effect maintains legibility.

With .buttonStyle(.glassProminent), the button material doesn’t properly respect the circular shape when defined only with the label view or with a shape in the glassEffect; artifacts appear when pressed. The practical solution proposed is to clip the button: clipShape(Circle()). This forces the material and highlights of the “glass” style to follow the circle at rest and during interaction.

    Button { } label: {
        Image(systemName: "heart.fill")
            .frame(width: 44, height: 44)
    }
    .buttonStyle(.glassProminent)
    .buttonBorderShape(.circle) // =>  doesn't properly respect the circular shape
    .tint(.red)
    .clipShape(Circle()) // => The solution for now is to clip the shape of the button into a circle

Tips

  • Don’t re-enable interactive on standard Button—it already reacts; reserve it for custom glassy surfaces that are truly tappable.

  • Tint with intent (primary/state), not as decoration. Let the system’s vibrant color do the legibility work.

  • In toolbars, keep icons monochrome by default, group actions with ToolbarSpacer, and avoid custom backgrounds so the scroll edge effect preserves clarity.

    ZStack {
        Rectangle()
            .fill(Color.yellow.gradient)

        VStack(spacing: 24) {
            // With transparency but responds like liquid glass
            HStack(spacing: 24) {
                Button {  } label: {
                    Text("Press me")
                }
                .controlSize(.large)
                .buttonStyle(.glass)
                .tint(.gray)

                Button {  } label: {
                    Text("Press me")
                }
                .controlSize(.large)
                .buttonStyle(.glass)
            }
            // No transparency, but responds like liquid glass
            HStack(spacing: 24) {
                Button {  } label: {
                Text("Press me")
                }
                .buttonStyle(.glassProminent)
                .tint(.orange)

                Button {  } label: {
                    Text("Press me")
                }
                .buttonStyle(.glassProminent)
            }
        }
        .frame(maxHeight: 220)

📦 All samples are available on GitHub

.glassEffect: shape, tint, and interactivity

Shape

The effect uses a capsule by default. You can pass any Shape (e.g., RoundedRectangle(.continuous)) via the in: parameter, but try to maintain corner concentricity with its container (avoid pinched radii or flare).

Tip: use in: to make geometry explicit—in: .capsule, .circle, .ellipse, .rect, or a rounded rectangle with your corner radius—so the surface matches its context and interaction.

Tint (.tint)

On glassy views, tint produces a vibrant color that adapts to the background. Apply it only with semantic intent, just like in toolbar buttons (primary, state)—not “for looks.”

Interactivity

Enable it only on tappable surfaces or interactive containers: the glass reacts with scale, bounce, and shimmer, aligning with system buttons and sliders. Buttons already include these reactions by default, so you don’t need to re-apply them. Avoid turning it on for static labels or dense lists—it increases GPU work and can distract from primary actions.

Grouping multiple glass elements: GlassEffectContainer

Physical rule: glass samples an area larger than its container and can’t sample another glass. If you have several “bubbles” close together, put them in a GlassEffectContainer to share a sampling region and avoid artifacts. For fluid transitions and morphing, use glassEffectID with a Namespace.

ZStack {
    Rectangle()
        .fill(Color.orange.gradient)

    VStack(spacing: 24) {
        // With transparency but responds like liquid glass
        HStack(spacing: 24) {
            Button {  } label: {
                Text("Press me")
            }
            .controlSize(.large)
            .buttonStyle(.glass)
            .tint(.gray)

            Button {  } label: {
                Text("Press me")
            }
            .controlSize(.large)
            .buttonStyle(.glass)
        }
        // No transparency, but responds like liquid glass
        HStack(spacing: 24) {
            Button {  } label: {
                Text("Press me")
            }
            .buttonStyle(.glassProminent)
            .tint(.orange)

            Button {  } label: {
                Text("Press me")
            }
            .buttonStyle(.glassProminent)
        }

        GlassEffectContainer {
            HStack(spacing: 24) {
                Button("Inherit") { }
                    .buttonStyle(.glass)

                Button("Indigo") { }
                    .buttonStyle(.glass)
                    .tint(.blue) // override
            }
        }
        .tint(.white)  // by default
    }
}

📦 All samples are available on GitHub

Handy micro-sections

When to use Clear (and why a bit of dimming helps)

Use it on small surfaces over rich content (e.g., photos/maps) to let the background “breathe.” Add a subtle local dimming behind the glass when contrast might be compromised, and give the element a touch of padding so the surface reads clearly.

How to disable Liquid Glass (and when it makes sense)

Apple doesn’t present disabling as a “third design variant”; it’s an implementation decision:

  • Apply glassEffect conditionally based on state (disabled, secondary) or context (dense lists, very vivid backgrounds).

  • Use it sparingly in low power mode or when Accessibility (Reduce Transparency / Increase Contrast / Reduce Motion) calls for it.

Reason: it clarifies hierarchy and reduces graphics load when glass doesn’t add value.

Apple emphasize adopting what the system already provides and only adjusting where it truly helps.

Quick anti-patterns

  • “Glass on glass” without a shared container → inconsistent sampling. Use GlassEffectContainer.

  • Mixing regular and clear within the same control set → confusing hierarchy.

  • Tint without semantics: visual noise; tint only when it means something.

  • Custom backgrounds/blur behind toolbars break the scroll edge effect. Remove them.

  • Interactivity on passive content distraction/rendering cost. Enable it only on tappable elements.

Final recommendations

  • Start with the standard pieces: app structures, toolbars, search placements, and system controls; remove backgrounds behind bars.

  • Apply glassEffect to custom views when it complements the navigation/controls layer, and keep shape concentricity.

  • Use regular by default, clear for small surfaces over rich content (+ dimming if needed).

  • Group glass with GlassEffectContainer and use glassEffectID for smooth morphing.

  • Buttons: .glass for secondary actions, .glassProminent for the primary; use .tint only with semantics.

  • Verify Accessibility (Increase Contrast, Reduce Transparency, Reduce Motion): the material and animations must remain legible and comfortable.

  • Use .buttonStyle(.glass) for common actions and .glassProminent for the primary one; tint with .tint if you need clear hierarchy.

Official catalog (Apple)

  • WWDC25 Build a SwiftUI app with the new design Covers glassEffect, tint with meaning, interactive, GlassEffectContainer, glassEffectID, button styles .glass / .glassProminent, concentricity, and practical adoption tips (remove backgrounds behind bars, edge effects).
  • WWDC25 Get to know the new design system Principles of the new visual language, Liquid Glass in the UI architecture, concentricity (capsules, concentric shapes), monochrome iconography, and the role of scroll edge effects to separate UI and content.

Source code & samples

  • Series repository: All samples repository are available on GitHub

Meta

Updated: