Skip to content

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:

  1. Uses native Liquid Glass on iOS 26+
  2. Provides a consistent fallback for iOS 18-25
  3. Keeps your view code clean and simple

The Solution

Here's a View extension that handles both cases elegantly:

swift
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

swift
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+)

swift
self.glassEffect(interactive ? .regular.interactive() : .regular, in: shape)
    .clipShape(shape)

On iOS 26+, we use the native modifier with:

  • .regular variant 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)

swift
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

swift
Button("Add Item") {
    // action
}
.padding()
.glassedEffect(in: Capsule())

Interactive Floating Action Button

swift
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

swift
VStack(alignment: .leading) {
    Text("Statistics")
        .font(.headline)
    Text("Your weekly summary")
        .font(.subheadline)
        .foregroundStyle(.secondary)
}
.padding()
.glassedEffect(in: RoundedRectangle(cornerRadius: 16))

Circular Icon Button

swift
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:

swift
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.

References