โ† All articles

Liquid Glass in SwiftUI: A Practical Guide for iOS 27

Adopt Liquid Glass in SwiftUI on iOS 27. glassEffect, GlassEffectContainer, morphing button groups, tinted glass, and when to leave it alone.

Liquid Glass in SwiftUI: A Practical Guide for iOS 27

Liquid Glass is the material Apple introduced across iOS, iPadOS, macOS, and watchOS: a translucent surface that refracts and reflects the content behind it instead of just blurring it, and that reshapes itself as it moves, merges, and separates on screen. Every system control picked it up automatically the moment an app rebuilt against the new SDK, and by iOS 27 it is no longer the new thing, it is the baseline. Toolbars, tab bars, sheets, sliders, and alerts all render in it whether you touch a single line of code or not.

The part that takes deliberate work is your own chrome, the floating action buttons, badges, and custom bars that are not standard controls. SwiftUI exposes Liquid Glass through a small, composable API: glassEffect, GlassEffectContainer, and glassEffectID. This guide covers what you get for free, how to apply the material to custom views, how to make separate glass shapes morph into one another, and where restraint matters more than adoption.

On this page

What Liquid Glass actually is

Liquid Glass is not a blur modifier with better marketing. A blur samples the pixels behind a view and softens them. Liquid Glass samples them too, but it also lenses light through the shape's edges, brightens or dims to stay legible against whatever is underneath, and it is dynamic: pull two glass shapes near each other and they pull toward a shared boundary instead of overlapping like flat layers. It reacts to Dark Mode, to motion, and to the specific shape you draw it inside.

That last part matters for how you use it in SwiftUI. Glass is always applied in a shape, .circle, .rect(cornerRadius:), a Capsule(), because the material needs the shape's edges to know where to lens the light. There is no shapeless "make this glassy" modifier.

Step 1: What you get for free

Before writing any glass code, know what you are not responsible for. NavigationStack toolbars, TabView, .sheet, .alert, Slider, and system-provided buttons and menus all adopt Liquid Glass automatically once your app targets the current SDK. No modifier, no opt-in flag.

NavigationStack {
    List(items) { item in
        ItemRow(item: item)
    }
    .toolbar {
        ToolbarItem(placement: .primaryAction) {
            Button("Add", systemImage: "plus") { addItem() }
        }
    }
}

That toolbar, its button, and the tab bar below it render in Liquid Glass with zero lines changed from how you wrote toolbars before. The work in this guide is entirely about the views you draw yourself: a floating capture button, a custom badge, a bespoke bottom bar that is not built from TabView.

Step 2: Apply glassEffect to a custom view

glassEffect(_:in:) is the entry point. Give it a style and a shape.

Text("42")
    .font(.system(size: 34, weight: .bold, design: .rounded))
    .padding(20)
    .glassEffect(.regular, in: .rect(cornerRadius: 20))

.regular is the standard material. Tint it to carry a brand or status color, and add .interactive() so the glass responds to touch, brightening slightly under a tap the same way a system glass button does.

Image(systemName: "heart.fill")
    .padding(16)
    .glassEffect(.regular.tint(.pink).interactive(), in: .circle)

Keep the tint subtle. Liquid Glass is built to let the background show through; a heavily saturated tint fights the material instead of using it, and starts to look like a flat colored circle with extra rendering cost.

Step 3: Group and morph with GlassEffectContainer

A single glass view looks right in isolation, but put two or three next to each other without help and they render as separate, competing panes of glass instead of one coherent surface. GlassEffectContainer fixes that: it groups the glass views inside it so they sample and blend as one system.

GlassEffectContainer(spacing: 16) {
    HStack(spacing: 16) {
        Image(systemName: "play.fill")
            .padding()
            .glassEffect(.regular, in: .circle)
 
        Image(systemName: "forward.fill")
            .padding()
            .glassEffect(.regular, in: .circle)
    }
}

The container also unlocks morphing. Tag each glass view with a stable ID inside a shared namespace, and when a view enters or leaves the container, SwiftUI animates the glass shapes merging or splitting instead of cross-fading.

@Namespace private var glassNamespace
@State private var isExpanded = false
 
GlassEffectContainer(spacing: 16) {
    HStack(spacing: 16) {
        Image(systemName: "play.fill")
            .padding()
            .glassEffect(.regular, in: .circle)
            .glassEffectID("play", in: glassNamespace)
 
        if isExpanded {
            Image(systemName: "forward.fill")
                .padding()
                .glassEffect(.regular, in: .circle)
                .glassEffectID("forward", in: glassNamespace)
        }
    }
}
.onTapGesture { isExpanded.toggle() }

Toggling isExpanded here does not just fade the second icon in, the two glass circles visibly pull apart from a single merged shape into two, which is the effect you have seen in the system's own expanding control clusters. The GlassEffectContainer and matching IDs are what makes it possible; without them the same code just pops the second icon in.

Step 4: Glass buttons and toolbars

For plain buttons, skip glassEffect entirely and reach for the built-in style.

Button("Continue") {
    proceed()
}
.buttonStyle(.glass)

.buttonStyle(.glass) gives you the same material, tap response, and shape handling as a system button, without managing a shape or an interactive modifier yourself. Use glassEffect directly when you are building something that is not a button, a badge, a floating info card, a custom progress capsule, and reserve .buttonStyle(.glass) for things a user taps.

For a custom bottom bar that is not a TabView, wrap the whole bar in one GlassEffectContainer rather than applying glassEffect to each icon separately. One container reading as one surface is the difference between it looking like a system control and looking like a stack of loose glass chips.

Step 5: Know when to leave it alone

Three situations where more glass is worse.

Text over busy backgrounds. Glass is legible by design over most content, but small text on a glass surface sitting above a high-contrast photo or video can still lose readability at a glance. If a label needs to be read instantly, back it with .regular material at full opacity or a solid scrim rather than glass.

Reduce Transparency and Increase Contrast. Both accessibility settings change how Liquid Glass renders, dialing back translucency or boosting the material's contrast automatically. You do not need custom code to support them, the system handles it, but you do need to test with them on. Settings โ†’ Accessibility โ†’ Display & Text Size on a simulator or device, then walk your custom glass views and confirm nothing goes illegible.

Glass on glass. Nesting a glassEffect view inside another glass surface (a glass button inside a glass sheet, say) usually looks muddier than either alone. If a view is already sitting on a system glass surface like a sheet or a toolbar, it often reads better with a plain background or none at all.

What iOS 27 refined

Liquid Glass shipped as a system-wide material at WWDC25 and iOS 27 is the cycle where the rough edges from year one got sanded down. Rendering cost dropped on lower-power devices, so glass-heavy view hierarchies that throttled or dropped frames on older hardware run smoother without any code changes on your end. GlassEffectContainer also blends more predictably when containers are nested inside one another, an edge case that produced visible seams in the first release and is now handled by the framework directly. None of this changes the API surface above, code written against the original glassEffect and GlassEffectContainer APIs behaves the same, it simply renders better on the devices your app already ships to.

For the wider WWDC26 toolchain, see the Xcode 27 guide and what's new in Swift.

FAQ

Do I have to opt in to Liquid Glass?

Not for system controls. Toolbars, tab bars, sheets, alerts, and standard buttons adopt it automatically once your app is built against the current SDK. You only write glassEffect code for custom views that are not built from those system components.

What is the difference between glassEffect and buttonStyle(.glass)?

glassEffect(_:in:) is the general-purpose modifier for any view, applied inside a shape you choose. .buttonStyle(.glass) is a preset built on top of it specifically for buttons, handling the shape and tap response for you. Use the button style for buttons, glassEffect for everything else.

Why do my two glass views look like separate panes instead of one surface?

Wrap them in a GlassEffectContainer. Without it, adjacent glassEffect views sample and render independently, even if they are visually touching. The container is what makes them blend as one coherent material.

Does Liquid Glass work with Dark Mode?

Yes, automatically. The material samples whatever is behind it and adjusts brightness and contrast to stay legible in both light and dark contexts. No separate dark-mode handling is needed.

Will Liquid Glass tank performance on older devices?

It has a real rendering cost, more than a flat color or a simple blur, but iOS 27 reduced that cost meaningfully on lower-power hardware. Keep glass to intentional surfaces, floating controls and chrome, rather than applying it to every row in a long list, and profile with Instruments if a glass-heavy screen feels janky.

Can I use glassEffect on a whole screen background?

You can, but it is rarely the right call. Liquid Glass is built to read as a distinct surface floating above content, not as the content layer itself. Reserve it for chrome, controls, and small floating elements, and let the content underneath stay opaque.

Liquid Glass rewards restraint more than coverage. The system gives you the material for free everywhere it already belongs, toolbars, tab bars, sheets, and the handful of custom surfaces worth the extra code are the floating controls a user's eye is meant to land on first.


Spaceport generates a production-ready SwiftUI Xcode project built against the current SDK, so every standard control, toolbar, and tab bar renders in Liquid Glass from the first build, alongside RevenueCat subscriptions, Sign in with Apple and Google, notifications, an AI assistant, onboarding, and App Store Connect pricing across 25 markets. From an indie iOS dev, for indie iOS devs.

Read more at spaceport.build

Community appsJoin Discord