All Articles
CategoryiOS
Reading Time
13 min read
Published
2026-07-09
Word Count
1,583words

Grab a coffee — this one is a deep dive!

iOS 26 Liquid Glass: A Guide to Adapting Your SwiftUI App to the New Material System

Summary

From glassEffect APIs to tabBarMinimizeBehavior, from the UIDesignRequiresCompatibility opt-out to GlassEffectContainer performance traps — the real decisions I faced migrating a production SwiftUI codebase to iOS 26's Liquid Glass language.

  • The glassEffect(_:in:) modifier is available on iOS 26.0+ with .regular/.clear/.identity variants.
  • Overlapping glass surfaces must be wrapped in a GlassEffectContainer — glass cannot correctly sample glass.
  • With ConcentricRectangle, inner/outer corner radius can be aligned automatically to the container.
  • UIDesignRequiresCompatibility preserves the old look, but per Apple it is not a permanent solution.
iOS 26 Liquid Glass: A Guide to Adapting Your SwiftUI App to the New Material System

# iOS 26 Liquid Glass: A Guide to Adapting Your SwiftUI App to the New Material System

At WWDC25, Apple announced its biggest design-language shift since iOS 7: Liquid Glass. This single material system spans iOS 26, iPadOS 26, macOS Tahoe, watchOS 26, tvOS 26, and visionOS 26, and works by bending light in real time (lensing), reacting to device motion with specular highlights, and adapting shadows to the content beneath it. Any app compiled against the Xcode 26 SDK inherits this material automatically — whether you want it to or not.

In this piece I share the concrete decisions, APIs, and pitfalls I ran into while migrating a production SwiftUI codebase to Liquid Glass. No speculation — only information verified against Apple's official documentation and WWDC25 session 323 ("Build a SwiftUI app with the new design").

Why "adaptation" is needed, not just "automatic"

Any app compiled with Xcode 26 and targeting iOS 26 automatically gets Liquid Glass on standard UIKit/SwiftUI controls (buttons, tab bars, toolbars, navigation bars). The catch: custom-drawn controls, hand-managed toolbar layouts, and old material hacks like .background(.ultraThinMaterial) don't benefit from this transition — and often clash with the new system, producing visual artifacts (double blur, lost contrast, wrong corner radius). The adaptation work boils down to not manually fighting what the system already does for you, and re-expressing genuinely custom surfaces (cards, floating action buttons, custom control groups) with the new APIs.

The glassEffect API family

The core modifier, glassEffect(_:in:), is available from iOS 26.0+ and defaults to the .regular variant with a Capsule shape:

struct FloatingActionButton: View {

var body: some View {

Image(systemName: "plus")

.font(.title2.weight(.semibold))

.frame(width: 56, height: 56)

.glassEffect(.regular.tint(.blue).interactive())

}

}

There are three base variants:

Variant
Use case
.regular
Default, suitable for most buttons/cards
.clear
Media-heavy surfaces (controls over photo/video); always pair with a dimming layer underneath
.identity
Disables the effect while preserving only the shape/clipping behavior

.tint(_:) adds semantic coloring, and .interactive() turns on scale/bounce/shimmer behavior that responds to touch. In the modifier chain, `.glassEffect()` should always come last — shape first, then padding, glass last.

Pro Tip

Glass renders by sampling the content beneath it. That means glass cannot sample glass — if you stack two glass views directly on top of each other, the bottom one won't refract light correctly. Always wrap adjacent/overlapping glass elements in a GlassEffectContainer.

GlassEffectContainer and morphing

To combine multiple glass shapes into a single surface and morph them into one another, use GlassEffectContainer. A namespace-based glassEffectID defines the transition animation between elements:

struct ExpandingToolGroup: View {

@Namespace private var glassNamespace

@State private var isExpanded = false

var body: some View {

GlassEffectContainer(spacing: 16) {

HStack(spacing: 16) {

Button(action: { isExpanded.toggle() }) {

Image(systemName: "wand.and.stars")

}

.glassEffect()

.glassEffectID("trigger", in: glassNamespace)

if isExpanded {

Button(action: {}) { Image(systemName: "scissors") }

.glassEffect()

.glassEffectID("crop", in: glassNamespace)

Button(action: {}) { Image(systemName: "slider.horizontal.3") }

.glassEffect()

.glassEffectID("adjust", in: glassNamespace)

}

}

}

}

}

Use GlassEffectContainer in a single place, wide enough to cover all the related glass views. Wrapping each button in its own separate container both breaks the morphing animation and creates unnecessary rendering cost — each container triggers its own compositing pass.

Corner concentricity

The most easily overlooked detail of Liquid Glass is corner radius. When you place a glass surface inside a container (card, sheet, border), the inner corner radius needs to be concentric with the outer container's corner — otherwise the two curves intersect and it optically feels "off." iOS 26 introduces a new shape for this: ConcentricRectangle. Instead of hardcoding a cornerRadius, you can align the glass shape to its container automatically:

struct GlassCard<Content: View>: View {

@ViewBuilder var content: Content

var body: some View {

content

.padding(20)

.glassEffect(.regular, in: .rect(cornerRadius: .containerConcentric))

}

}

.rect(cornerRadius: .containerConcentric) derives the corner radius from the outer container's shape and the padding distance between them. Using this instead of a hardcoded cornerRadius: 16 keeps corners consistent with system cards across different screen sizes and Dynamic Type scales. Manual radius values are one of the most common "small but everywhere" bugs fixed during a Liquid Glass migration.

Tab bar and toolbar behavior changes

TabView and toolbars changed structurally in iOS 26. Two APIs stand out in production in particular:

1. `tabBarMinimizeBehavior(_:)` — shrinks the tab bar out of the way as the user scrolls content down:

TabView {

Tab("Feed", systemImage: "square.stack") { FeedView() }

Tab("Search", systemImage: "magnifyingglass", role: .search) { SearchView() }

}

.tabBarMinimizeBehavior(.onScrollDown)

2. `role: .search` — visually separates the search tab from the others and turns it into a search field when selected. If search isn't your primary experience, you can collapse the search bar down to a small icon on scroll with searchToolbarBehavior(.minimize).

On the toolbar side, items now render on a Liquid Glass surface that floats above the content and automatically adjusts contrast against what's underneath. Old code that manually sets a toolbar background, like .background(Color(.systemBackground).opacity(0.8)), clashes with the new system and produces a doubled-up blurry look — these manual background modifiers need to be removed entirely.

Backward-compatibility strategy

There are three scenarios, and each calls for a different decision:

The new design is Apple's long-term direction. Use the new APIs conditionally with #available(iOS 26, *) checks, falling back to the old look on earlier OS versions:

if #available(iOS 26, *) {

content.glassEffect(.regular.interactive())

} else {

content.background(.ultraThinMaterial, in: Capsule())

}

2. Temporary opt-out: `UIDesignRequiresCompatibility`

Setting UIDesignRequiresCompatibility = YES in Info.plist keeps standard system controls, like UIButton, in their pre-iOS 26 appearance. This is a reasonable bridge for apps with a large custom design system, but not a permanent solution — Apple's roadmap signals removing this flag eventually, so it should only be used to buy migration time, not as a lasting architectural decision.

3. Custom rendering layer (non-SwiftUI / cross-platform)

Layers that draw their own controls on top of UIKit, or cross-platform layers like Flutter/React Native, cannot fully disable Liquid Glass at the system level — Apple enforces this material on all standard system windows regardless. All you can do is protect your own custom-drawn controls with the opt-out flag and accept the new behavior on surfaces that touch Apple's SDK controls.

Performance and common mistakes

The three most common mistakes I saw in production:

  1. A separate `glassEffect()` on every row/cell — applying glass individually to every cell in a List causes serious compositing overhead during scrolling. Reserve glass for a small number of fixed floating surfaces (toolbar, FAB, control group), not list cells.
  2. Using the `.clear` variant without a dimming layer — over bright/light content, text contrast drops below WCAG thresholds. .clear should always be paired with a darkening layer.
  3. Leaving old `.ultraThinMaterial`/`.regularMaterial` backgrounds in the same view tree alongside new glass modifiers — stacking two material systems on top of each other hurts both performance and visual consistency. During migration, systematically scan for and remove old material backgrounds.

Before/after migration: a concrete comparison

The table below summarizes the old and new approach for the five surfaces I touched most often in a production codebase I worked on:

Surface
iOS 25 and earlier
iOS 26 Liquid Glass
Floating action button
.background(.ultraThinMaterial, in: Circle())
.glassEffect(.regular.tint(.blue).interactive())
Tab bar
Fixed height, manual .toolbarBackground
tabBarMinimizeBehavior(.onScrollDown), system manages it automatically
Search tab
Separate .searchable modifier + manual placement
Tab(..., role: .search) + searchToolbarBehavior(.minimize)
Control group (crop/adjust/filter)
Separate Buttons + HStack, manual background
Single GlassEffectContainer morphing via glassEffectID
List cell background
.regularMaterial where needed
Left untouched — glass isn't applied to list cells

This table really shows the essence of the migration: the change isn't "add glass everywhere," it's recognizing which surfaces are the system's new primary citizens. High-density repeating content like list cells and form rows isn't glass's target area; floating controls, toolbars, and tab bars are.

Accessibility and Reduce Transparency

Liquid Glass's specular highlights and real-time light refraction interact with the Reduce Transparency setting under Settings > Accessibility > Display & Text Size. System controls honor this preference automatically, but for custom surfaces you draw yourself with glassEffect, you need to test this behavior — don't ship a glass surface to production without verifying your UI stays readable with Reduce Transparency turned on in the simulator. Likewise, with Increase Contrast on, make sure colors passed via .tint() don't drop text/icon contrast below the WCAG AA threshold.

Conclusion

Liquid Glass isn't an optional visual refresh — it's the default behavior for every SwiftUI app compiled against the Xcode 26 SDK. The real work is re-expressing genuinely custom surfaces with the glassEffect, GlassEffectContainer, and glassEffectID trio, without breaking the gains the system already gives you for free. Structural APIs like tabBarMinimizeBehavior and role: .search modernize the navigation layer almost for free; UIDesignRequiresCompatibility is only a bridge that buys transition time, not a permanent architecture.

Before starting a production migration: inventory your old material backgrounds, draw your glass container boundaries deliberately, and avoid using glass in list/collection cells. These three rules are the shortest path to adopting Liquid Glass without a performance regression.


*Sources: Apple Developer Documentation (glassEffect(_:in:)), GlassEffectContainer, Applying Liquid Glass to custom views) and WWDC25 Session 323, "Build a SwiftUI app with the new design."*

Tags

#SwiftUI#iOS 26#Liquid Glass#UIKit#Design System
Muhittin Çamdalı

Muhittin Çamdalı

Lead Mobile Engineer

Lead Mobile Engineer with 12+ years of experience. Expert in iOS, Android and cross-platform architectures with Swift, SwiftUI, Kotlin and Flutter. I build performant, user-friendly mobile apps.

iOS Development News

Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.

We respect your privacy. You can unsubscribe at any time.

Share