iOS 26 Glass Effect with Backward Compatibility in SwiftUI
Introduction
iOS 26 introduced Liquid Glass, Apple's most significant design evolution since iOS 7. This translucent, dynamic material reflects and refracts surrounding content, creating a sophisticated visual effect for navigation elements and controls.
While the new .glassEffect() modifier makes implementing this design straightforward, most apps still need to support older iOS versions. This article shows you how to create a reusable View extension that uses native Liquid Glass on iOS 26+ and falls back to .thinMaterial on older versions.
The Challenge
When you try to use .glassEffect() on iOS versions before 26, your app won't compile. You need a solution that:
- Uses native Liquid Glass on iOS 26+
- Provides a consistent fallback for iOS 18-25
- Keeps your view code clean and simple
The Solution
Here's a View extension that handles both cases elegantly:
import SwiftUI
extension View {
/// Applies a glass effect with forward compatibility.
/// - On iOS 26+: Uses native `.glassEffect()` for liquid glass appearance
/// - On iOS 18-25: Falls back to `.thinMaterial` for consistency
@ViewBuilder
func glassedEffect(in shape: some Shape, interactive: Bool = false) -> some View {
if #available(iOS 26.0, *) {
self.glassEffect(interactive ? .regular.interactive() : .regular, in: shape)
.clipShape(shape)
} else {
background {
shape.fill(.thinMaterial)
}
.clipShape(shape)
}
}
}How It Works
Let's break down the key components:
1. The @ViewBuilder Attribute
The @ViewBuilder attribute allows the function to return different view types depending on the iOS version. Without it, Swift would require both branches to return the same type.
2. Availability Check
if #available(iOS 26.0, *) {This compile-time check ensures the Liquid Glass code only runs on iOS 26+. The compiler knows this is safe and allows the .glassEffect() modifier in that branch.
3. Native Liquid Glass (iOS 26+)
self.glassEffect(interactive ? .regular.interactive() : .regular, in: shape)
.clipShape(shape)On iOS 26+, we use the native modifier with:
.regularvariant for standard glass appearance.interactive()for controls that should respond to touch with scaling and shimmer effects- The shape parameter controls the glass boundary
4. Material Fallback (iOS 18-25)
background {
shape.fill(.thinMaterial)
}
.clipShape(shape)For older versions, .thinMaterial provides a similar translucent effect that blends well with the system design.
Usage Examples
Basic Button with Glass Effect
Button("Add Item") {
// action
}
.padding()
.glassedEffect(in: Capsule())Interactive Floating Action Button
Button(action: addItem) {
HStack(spacing: 8) {
Image(systemName: "plus")
.font(.system(size: 20, weight: .semibold))
Text("Add Level")
}
.foregroundStyle(Color.primary)
.padding(.horizontal, 20)
.frame(height: 58)
.glassedEffect(in: Capsule(), interactive: true)
.shadow(color: Color.black.opacity(0.12), radius: 12, y: 8)
}Card with Rounded Corners
VStack(alignment: .leading) {
Text("Statistics")
.font(.headline)
Text("Your weekly summary")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding()
.glassedEffect(in: RoundedRectangle(cornerRadius: 16))Circular Icon Button
Button(action: showSettings) {
Image(systemName: "gear")
.font(.title2)
.frame(width: 44, height: 44)
.glassedEffect(in: Circle(), interactive: true)
}Design Considerations
When to Use Glass Effects
According to Apple's design guidelines, Liquid Glass should be reserved for the navigation layer, not for main content. Think toolbars, floating action buttons, and control overlays.
Avoid Nesting Glass
Glass cannot sample other glass elements. If you have multiple glass elements near each other, wrap them in a GlassEffectContainer on iOS 26+ to ensure consistent visual behavior.
Extending for More Options
You can expand the extension to support more glass variants:
extension View {
@ViewBuilder
func glassedEffect(
in shape: some Shape,
variant: GlassVariant = .regular,
interactive: Bool = false
) -> some View {
if #available(iOS 26.0, *) {
let glass: Glass = switch variant {
case .regular: .regular
case .clear: .clear
}
self.glassEffect(interactive ? glass.interactive() : glass, in: shape)
.clipShape(shape)
} else {
background {
shape.fill(.thinMaterial)
}
.clipShape(shape)
}
}
}
enum GlassVariant {
case regular
case clear
}Conclusion
Supporting iOS 26's Liquid Glass while maintaining backward compatibility doesn't require complex abstractions. A simple View extension with @ViewBuilder and #available checks gives you the best of both worlds: native Liquid Glass on newer devices and a graceful material fallback on older ones.
This pattern keeps your view code clean—just call .glassedEffect(in: Capsule()) and let the extension handle the rest.

