At WWDC 2025, Apple unveiled Liquid Glass - the most significant design evolution since iOS 7. This new visual language brings a translucent, dynamic material to controls, navigation, icons, and more. Here's everything you need to know about implementing Liquid Glass in your iOS 26 SwiftUI apps.
What is Liquid Glass?
Liquid Glass is Apple's new design material that reflects and refracts its surroundings while dynamically transforming to bring focus to content. It creates a new level of vitality across the entire interface.
Key characteristics:
- Translucent - Content behind glass elements shows through with a blur effect
- Dynamic - The material responds to what's beneath it in real-time
- Morphing - Glass elements can smoothly transition between states
- Ambient - Subtle reflections and refractions create depth
Liquid Glass extends across iOS 26, iPadOS 26, macOS Tahoe, watchOS 26, and tvOS 26 - creating visual harmony while respecting each platform's unique qualities.
Automatic Adoption
Great news: if your app already uses native SwiftUI controls, you get Liquid Glass automatically. Simply recompile against the iOS 26 SDK and these components adopt the new design:
- Tab bars
- Toolbars
- Navigation bars
- Sheets
- Alerts
- System buttons
Toolbar items now float on a Liquid Glass surface that automatically adapts to content beneath. Partial-height sheets get an inset glass background that morphs as the sheet expands.
Core SwiftUI APIs
iOS 26 introduces three key APIs for working with Liquid Glass:
1. glassEffect() Modifier
Apply the glass material to any view:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, Glass!")
.padding()
}
.glassEffect()
}
}
The glassEffect() modifier transforms your view with the translucent glass material.
2. GlassEffectContainer
Coordinate how multiple glass elements blend and transition:
struct CardView: View {
var body: some View {
GlassEffectContainer {
VStack(spacing: 16) {
HeaderView()
.glassEffect()
ContentView()
.glassEffect()
FooterView()
.glassEffect()
}
}
}
}
Elements within a GlassEffectContainer share a visual style and can participate in coordinated animations.
3. glassEffectID() for Morphing
Create smooth morphing transitions between glass elements across states:
struct MorphingCard: View {
@Namespace private var animation
@State private var isExpanded = false
var body: some View {
GlassEffectContainer {
if isExpanded {
ExpandedCard()
.glassEffect()
.glassEffectID("card", in: animation)
} else {
CollapsedCard()
.glassEffect()
.glassEffectID("card", in: animation)
}
}
.onTapGesture {
withAnimation(.spring()) {
isExpanded.toggle()
}
}
}
}
The glassEffectID associates related glass elements, enabling the system to morph between them seamlessly.
Building a Glass Card Component
Here's a complete example of a reusable glass card:
struct GlassCard<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
content
.padding(20)
.glassEffect()
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}
// Usage
struct ProfileView: View {
var body: some View {
ZStack {
// Background image for the glass to reflect
Image("background")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea()
GlassCard {
VStack(alignment: .leading, spacing: 12) {
HStack {
Image(systemName: "person.circle.fill")
.font(.largeTitle)
VStack(alignment: .leading) {
Text("John Appleseed")
.font(.headline)
Text("@john.bsky.social")
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
Divider()
Text("Building the future of social media")
.font(.body)
}
}
.padding()
}
}
}
Glass Toolbars and Navigation
Toolbars in iOS 26 automatically use Liquid Glass. Items are grouped on a floating glass surface:
struct FeedView: View {
var body: some View {
NavigationStack {
ScrollView {
// Your content
}
.navigationTitle("Feed")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(action: compose) {
Image(systemName: "square.and.pencil")
}
}
ToolbarItem(placement: .secondaryAction) {
Button(action: refresh) {
Image(systemName: "arrow.clockwise")
}
}
}
}
}
}
The toolbar items appear on a glass bar that floats above your content and responds to scrolling.
Glass Sheets
Sheets now feature glass backgrounds that adapt to their presentation height:
struct MainView: View {
@State private var showSettings = false
var body: some View {
ContentView()
.sheet(isPresented: $showSettings) {
SettingsView()
.presentationDetents([.medium, .large])
.presentationBackground(.glass)
}
}
}
At smaller heights, the sheet edges curve inward to nest within the display corners. When expanded to full height, the glass gradually becomes opaque and anchors to the screen edges.
Custom Glass Buttons
Create interactive glass buttons with hover and press states:
struct GlassButton: View {
let title: String
let icon: String
let action: () -> Void
@State private var isPressed = false
var body: some View {
Button(action: action) {
HStack(spacing: 8) {
Image(systemName: icon)
Text(title)
}
.padding(.horizontal, 20)
.padding(.vertical, 12)
.glassEffect()
.clipShape(Capsule())
.scaleEffect(isPressed ? 0.95 : 1.0)
}
.buttonStyle(.plain)
.onLongPressGesture(minimumDuration: .infinity, pressing: { pressing in
withAnimation(.easeInOut(duration: 0.1)) {
isPressed = pressing
}
}, perform: {})
}
}
// Usage
GlassButton(title: "Follow", icon: "plus") {
// Follow action
}
Animated Glass Transitions
Create dynamic transitions between glass states:
struct ExpandableCard: View {
@Namespace private var glassNamespace
@State private var isExpanded = false
var body: some View {
GlassEffectContainer {
VStack {
if isExpanded {
// Expanded state
VStack(spacing: 20) {
Image(systemName: "star.fill")
.font(.system(size: 60))
Text("Featured Content")
.font(.title)
Text("This card has expanded to show more details about the featured content.")
.multilineTextAlignment(.center)
Button("Collapse") {
withAnimation(.spring(response: 0.4)) {
isExpanded = false
}
}
}
.padding(40)
.glassEffect()
.glassEffectID("card", in: glassNamespace)
} else {
// Collapsed state
HStack {
Image(systemName: "star.fill")
Text("Featured")
Spacer()
Image(systemName: "chevron.right")
}
.padding(20)
.glassEffect()
.glassEffectID("card", in: glassNamespace)
.onTapGesture {
withAnimation(.spring(response: 0.4)) {
isExpanded = true
}
}
}
}
}
}
}
UIKit and SwiftUI Hybrid Implementation
Many production apps use both UIKit and SwiftUI. Here's how to integrate Liquid Glass in hybrid architectures:
Hosting SwiftUI Glass Views in UIKit
import UIKit
import SwiftUI
class GlassHostingController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let glassView = GlassOverlayView()
let hostingController = UIHostingController(rootView: glassView)
addChild(hostingController)
view.addSubview(hostingController.view)
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
hostingController.view.heightAnchor.constraint(equalToConstant: 80)
])
hostingController.didMove(toParent: self)
}
}
struct GlassOverlayView: View {
var body: some View {
HStack(spacing: 20) {
ForEach(["house", "magnifyingglass", "bell", "person"], id: \.self) { icon in
Image(systemName: icon)
.font(.title2)
}
}
.frame(maxWidth: .infinity)
.padding()
.glassEffect()
}
}
Using UIKit Views Behind Glass
struct MapWithGlassOverlay: View {
var body: some View {
ZStack {
// UIKit MapView wrapped for SwiftUI
MapViewRepresentable()
.ignoresSafeArea()
VStack {
Spacer()
// Glass controls floating over the map
HStack(spacing: 16) {
GlassButton(icon: "location.fill", action: centerOnUser)
GlassButton(icon: "map.fill", action: toggleMapStyle)
GlassButton(icon: "square.stack.3d.up", action: show3D)
}
.padding()
.glassEffect()
.clipShape(Capsule())
.padding(.bottom, 40)
}
}
}
}
Common Pitfalls and Debugging
Avoid these common mistakes when implementing Liquid Glass:
1. Stacking Glass on Glass
Glass cannot sample other glass elements, leading to visual artifacts:
// DON'T do this - glass stacking causes issues
VStack {
Text("Outer")
.glassEffect() // Parent glass
Text("Inner")
.glassEffect() // Nested glass - problematic!
}
// DO this instead - use GlassEffectContainer
GlassEffectContainer {
VStack {
Text("Outer")
.glassEffect()
Text("Inner")
// No glass here, or use different visual treatment
.background(.thinMaterial)
}
}
2. Forgetting GlassEffectContainer for Coordination
// Without container - glass effects don't coordinate
VStack {
Card1().glassEffect()
Card2().glassEffect() // May have inconsistent appearance
}
// With container - consistent glass styling
GlassEffectContainer {
VStack {
Card1().glassEffect()
Card2().glassEffect() // Visually coordinated
}
}
3. Missing glassEffectID Namespace
// This won't morph properly
struct BrokenMorphing: View {
@State private var isExpanded = false
var body: some View {
if isExpanded {
ExpandedView()
.glassEffect()
.glassEffectID("card", in: ???) // Missing namespace!
}
}
}
// Correct implementation
struct WorkingMorphing: View {
@Namespace private var glassNamespace // Define namespace
@State private var isExpanded = false
var body: some View {
GlassEffectContainer {
if isExpanded {
ExpandedView()
.glassEffect()
.glassEffectID("card", in: glassNamespace)
} else {
CollapsedView()
.glassEffect()
.glassEffectID("card", in: glassNamespace)
}
}
}
}
4. Glass Over Solid Backgrounds
// Glass looks flat over solid colors
ZStack {
Color.gray // Solid background - glass loses depth
Text("Hello")
.glassEffect()
}
// Glass shines over varied content
ZStack {
Image("photo") // Dynamic background content
.resizable()
Text("Hello")
.glassEffect() // Glass reflects and refracts beautifully
}
Debugging Tips
- Use Debug Hierarchy: Xcode's View Debugger shows glass layers and their relationships
- Check for overlapping glass: Multiple glass views at the same z-position cause artifacts
- Profile GPU usage: Instruments shows if glass effects are causing performance issues
- Test with Reduce Transparency: Ensure your UI is still usable when glass is disabled
Advanced Glass Techniques
Glass with Dynamic Content
struct LiveGlassCard: View {
@State private var posts: [Post] = []
var body: some View {
ScrollView {
LazyVStack(spacing: 12) {
ForEach(posts) { post in
PostRow(post: post)
.padding()
.glassEffect()
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}
.padding()
}
.background {
// Animated gradient behind glass
AnimatedGradient()
.ignoresSafeArea()
}
}
}
Glass Tab Bar with Badge
struct GlassTabBar: View {
@Binding var selectedTab: Int
let tabs: [(icon: String, badge: Int?)]
var body: some View {
HStack(spacing: 0) {
ForEach(tabs.indices, id: \.self) { index in
Button {
withAnimation(.spring(response: 0.3)) {
selectedTab = index
}
} label: {
ZStack(alignment: .topTrailing) {
Image(systemName: tabs[index].icon)
.font(.title2)
.foregroundStyle(selectedTab == index ? .primary : .secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
if let badge = tabs[index].badge, badge > 0 {
Text("\(badge)")
.font(.caption2.bold())
.foregroundStyle(.white)
.padding(4)
.background(.red)
.clipShape(Circle())
.offset(x: -8, y: 4)
}
}
}
.buttonStyle(.plain)
}
}
.padding(.horizontal)
.glassEffect()
.clipShape(Capsule())
}
}
Conditional Glass Based on Scroll Position
struct ScrollAwareGlassHeader: View {
@State private var scrollOffset: CGFloat = 0
var body: some View {
ZStack(alignment: .top) {
ScrollView {
GeometryReader { geo in
Color.clear.preference(
key: ScrollOffsetKey.self,
value: geo.frame(in: .named("scroll")).minY
)
}
.frame(height: 0)
// Your content here
ContentView()
.padding(.top, 60)
}
.coordinateSpace(name: "scroll")
.onPreferenceChange(ScrollOffsetKey.self) { offset in
scrollOffset = offset
}
// Header with conditional glass
HeaderView()
.padding()
.background {
if scrollOffset < -20 {
Rectangle()
.glassEffect()
.transition(.opacity)
}
}
.animation(.easeInOut(duration: 0.2), value: scrollOffset < -20)
}
}
}
Accessibility Considerations
Liquid Glass includes important accessibility features:
- Reduce Transparency - Users can reduce glass transparency in Settings for improved legibility
- Tinted vs Clear - Settings option to use tinted glass instead of clear
- Increase Contrast - Glass effects adapt when this accessibility setting is enabled
- Dynamic Type - Glass containers scale appropriately with text size changes
Always test your glass implementations with accessibility settings enabled:
// Preview with reduce transparency
#Preview("Reduce Transparency") {
ContentView()
.environment(\.accessibilityReduceTransparency, true)
}
Best Practices
Do:
- Use glass for floating UI - Toolbars, action buttons, overlays
- Provide contrasting backgrounds - Glass looks best over varied, colorful content
- Group related elements - Use GlassEffectContainer for coordinated effects
- Test legibility - Ensure text remains readable over all backgrounds
- Embrace the morphing - Use glassEffectID for smooth state transitions
Don't:
- Overuse glass - Not every element needs to be translucent
- Stack glass on glass - Multiple layers become muddy and hard to read
- Use glass for dense text - Long-form content should have solid backgrounds
- Ignore accessibility - Test with Reduce Transparency enabled
Migration from iOS 18
If your app already uses SwiftUI materials, here's how to migrate to Liquid Glass:
API Mapping Table
| iOS 18 API | iOS 26 Equivalent | Notes |
|---|---|---|
.background(.ultraThinMaterial) |
.glassEffect() |
Glass adapts automatically |
.background(.thinMaterial) |
.glassEffect() |
No thickness levels needed |
.background(.regularMaterial) |
.glassEffect() |
System handles opacity |
.background(.thickMaterial) |
.glassEffect() |
Or use solid background |
.matchedGeometryEffect() |
.glassEffectID() |
For glass-specific morphing |
| Custom blur effects | GlassEffectContainer |
Coordinated glass rendering |
Step-by-Step Migration
Step 1: Update your deployment target to iOS 26 in Xcode.
Step 2: Search your codebase for material usage:
// Find and replace patterns:
// OLD: .background(.ultraThinMaterial)
// NEW: .glassEffect()
// OLD: .background(.regularMaterial)
// NEW: .glassEffect()
// For matched geometry with glass:
// OLD: .matchedGeometryEffect(id: "card", in: namespace)
// NEW: .glassEffectID("card", in: namespace)
Step 3: Wrap related glass elements in containers:
// Before (iOS 18)
VStack {
Header().background(.thinMaterial)
Content().background(.thinMaterial)
}
// After (iOS 26)
GlassEffectContainer {
VStack {
Header().glassEffect()
Content().glassEffect()
}
}
Step 4: Test with accessibility settings enabled (Reduce Transparency, Increase Contrast).
Step 5: Profile performance and optimize if needed.
Backward Compatibility
If you need to support iOS 18 and iOS 26:
extension View {
@ViewBuilder
func adaptiveGlass() -> some View {
if #available(iOS 26, *) {
self.glassEffect()
} else {
self.background(.ultraThinMaterial)
}
}
}
// Usage
Text("Hello")
.padding()
.adaptiveGlass()
.clipShape(RoundedRectangle(cornerRadius: 12))
Performance Tips
Liquid Glass is GPU-accelerated but can impact performance:
- Limit animated glass - Don't animate too many glass elements simultaneously
- Use lazy loading - LazyVStack/LazyHStack for lists with glass cells
- Profile regularly - Use Instruments to monitor GPU usage
- Consider reduced motion - Simplify animations when this setting is enabled
Frequently Asked Questions
What is Apple Liquid Glass?
Liquid Glass is Apple's new design language in iOS 26, featuring translucent materials that reflect and refract surroundings for a more dynamic, immersive UI.
How do I add Liquid Glass to my SwiftUI app?
Use .glassEffect() on views, wrap related elements in GlassEffectContainer, and use .glassEffectID() for morphing transitions.
Will my app automatically get Liquid Glass?
Yes, for native controls like toolbars, sheets, and tab bars when you recompile against iOS 26 SDK. Custom views need manual implementation.
Does Liquid Glass work on all Apple platforms?
Yes - iOS 26, iPadOS 26, macOS Tahoe, watchOS 26, and tvOS 26 all support Liquid Glass with platform-appropriate adaptations.
Resources
- Apple Documentation: Applying Liquid Glass
- WWDC 2025: Build a SwiftUI app with the new design
- Apple Newsroom: Introducing Liquid Glass
Related Reading
Explore more iOS development guides from the Skyscraper blog:
- Building a Liquid Glass Tab Bar in iOS 26 - Practical implementation walkthrough
- UIKit vs SwiftUI for Building a Bluesky Client - Framework comparison and decision guide
- Apple HIG for Bluesky iOS Apps - Design best practices
- SwiftUI vs UIKit for Social Media Apps - Architecture considerations
- Creating App Icons with Apple Icon Composer - Icon design for iOS 26
- RevenueCat iOS Subscriptions Guide - Monetization for iOS apps