iOS 26 and macOS Tahoe introduce Liquid Glass—Apple's unified design language. This guide compiles official Apple guidelines from WWDC 2025 sessions to help you integrate Liquid Glass correctly.
What is Liquid Glass?
From Apple's WWDC 2025 Session 219:
"Liquid Glass is a new digital meta-material that dynamically bends and shapes light."
Key difference: Liquid Glass uses "Lensing"—bending and concentrating light, not scattering it like traditional blur.
Part 1: What You Get FREE (Zero Code)
Recompile with Xcode 26 and these get Liquid Glass automatically:
iOS 26:
- NavigationBar, TabBar, Toolbar
- Sheets, Popovers, Menus, Alerts
- Search bars, Control Center
- Toggles, Sliders, Pickers (during interaction)
macOS Tahoe:
- Toolbar, Sidebar, Menu bar, Dock
- Window controls, NSPopover, Sheets
// This automatically gets Liquid Glass!
NavigationStack {
List(items) { item in
Text(item.name)
}
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button("Add", systemImage: "plus") { }
}
}
}
Part 2: The Golden Rule - Navigation Layer Only
From Apple WWDC 2025:
"Liquid Glass is best reserved for the navigation layer that floats above the content of your app."
✅ Use Glass For:
- Toolbars, TabBars, Sidebars
- Floating action buttons
- Sheets, popovers, menus
❌ Never Use Glass For:
- Content layer (lists, cards, tables)
- Full-screen backgrounds
- Scrollable content
// ❌ WRONG - Glass on content
List {
ForEach(items) { item in
Text(item.name)
.glassEffect() // DON'T!
}
}
// ✅ CORRECT - Glass only on floating controls
ZStack {
List { /* content without glass */ }
VStack {
Spacer()
FloatingButton()
.glassEffect(.regular.interactive())
}
}
Part 3: Never Stack Glass on Glass
From Apple WWDC 2025:
"Always avoid glass on glass. Stacking Liquid Glass elements can quickly make the interface feel cluttered."
Why? Glass cannot properly sample other glass.
// ❌ WRONG
VStack {
HeaderView().glassEffect()
ContentView().glassEffect() // Glass on glass!
}
// ✅ CORRECT - Single glass layer
ZStack {
ContentView() // No glass
FloatingControls().glassEffect() // Single layer
}
Part 4: GlassEffectContainer - The Essential API
From Apple WWDC 2025 Session 323:
"Group multiple glass elements within a GlassEffectContainer because glass cannot sample other glass."
Always Use Container for Multiple Elements
// ✅ CORRECT
GlassEffectContainer {
HStack(spacing: 16) {
Button("Edit") { }.glassEffect()
Button("Share") { }.glassEffect()
Button("Delete") { }.glassEffect()
}
}
// ❌ WRONG - Without container
HStack {
Button("Edit") { }.glassEffect()
Button("Share") { }.glassEffect()
}
// Each samples independently = inconsistent!
Benefits of GlassEffectContainer
- Shared sampling region - Consistent appearance
- Better performance - Single render pass
- Enables morphing - Fluid transitions
- Automatic grouping - Elements blend when close
Spacing Parameter
GlassEffectContainer(spacing: 30) {
// Elements within 30pt morph together
HStack(spacing: 20) {
Button("A") { }.glassEffect()
Button("B") { }.glassEffect()
}
}
Part 5: Morphing with glassEffectID
Create fluid transitions between glass elements:
struct ExpandableMenu: View {
@State private var isExpanded = false
@Namespace private var namespace
var body: some View {
GlassEffectContainer(spacing: 20) {
HStack(spacing: 16) {
if isExpanded {
Button("Camera", systemImage: "camera") { }
.glassEffect(.regular.interactive())
.glassEffectID("camera", in: namespace)
Button("Photos", systemImage: "photo") { }
.glassEffect(.regular.interactive())
.glassEffectID("photos", in: namespace)
}
Button {
withAnimation(.bouncy) {
isExpanded.toggle()
}
} label: {
Image(systemName: isExpanded ? "xmark" : "plus")
.frame(width: 44, height: 44)
}
.buttonStyle(.glassProminent)
.buttonBorderShape(.circle)
.glassEffectID("toggle", in: namespace)
}
}
}
}
Requirements for morphing:
- Elements in same
GlassEffectContainer - Each has
glassEffectIDwith shared namespace - Animation applied to state changes
Part 6: Regular vs Clear Variants
From Apple WWDC 2025:
"They should never be mixed, as they each have their own characteristics."
Regular (Default) - Use for Most Cases
.glassEffect(.regular) // or just .glassEffect()
Clear - Only When ALL Three Conditions Met:
- Over media-rich content (photos, videos)
- Content won't suffer from dimming layer
- Content above is bold and bright
// Photo editor controls
ZStack {
Image("photo")
HStack {
Button("Filters") { }
.glassEffect(.clear.interactive())
}
}
Never Mix Variants
// ❌ WRONG
HStack {
Button("A") { }.glassEffect(.regular)
Button("B") { }.glassEffect(.clear) // Don't mix!
}
Part 7: Tinting - Primary Actions Only
From Apple WWDC 2025:
"Tinting should only be used to bring emphasis to primary elements and actions."
// ✅ CORRECT - Only primary action tinted
HStack {
Button("Cancel") { }
.buttonStyle(.glass) // No tint
Button("Save") { }
.buttonStyle(.glassProminent)
.tint(.blue) // Primary action
}
// ❌ WRONG - Everything tinted
HStack {
Button("A") { }.glassEffect(.regular.tint(.blue))
Button("B") { }.glassEffect(.regular.tint(.green))
Button("C") { }.glassEffect(.regular.tint(.red))
}
// When everything is tinted, nothing stands out!
Part 8: Button Styles
| Style | Appearance | Use Case |
|---|---|---|
.glass |
Translucent | Secondary actions |
.glassProminent |
Opaque | Primary actions |
HStack {
Button("Cancel") { }
.buttonStyle(.glass)
Button("Confirm") { }
.buttonStyle(.glassProminent)
.tint(.green)
}
Part 9: Interactive Modifier (iOS Only)
// iOS - Touch feedback
Button("Tap") { }
.glassEffect(.regular.interactive())
Behaviors: Scales, bounces, shimmers, illuminates from touch point.
Part 10: macOS Specifics
AppKit Glass Bezel
let button = NSButton()
button.bezelStyle = .glass
button.bezelColor = .systemBlue
Automatic Toolbar Grouping
AppKit groups multiple toolbar buttons on one glass piece. Use NSToolbarItemGroup or spacers to override.
Sidebar Ambient Reflection
On macOS/iPad, sidebars reflect light from nearby colorful content automatically.
Part 11: Accessibility - Automatic
From Apple WWDC 2025:
"These are available automatically whenever you use the new material."
| Setting | Effect |
|---|---|
| Reduce Transparency | Frostier glass |
| Increase Contrast | Black/white with border |
| Reduce Motion | Disabled elastic effects |
No extra code needed!
Quick Reference: Do's and Don'ts
✅ DO
// Container for multiple elements
GlassEffectContainer {
HStack {
Button("A") { }.glassEffect()
Button("B") { }.glassEffect()
}
}
// Morphing IDs
.glassEffectID("btn", in: namespace)
// Interactive on iOS
.glassEffect(.regular.interactive())
// Tint only primary actions
.buttonStyle(.glassProminent).tint(.blue)
❌ DON'T
// Glass on content
List { ... }.glassEffect()
// Glass on glass
VStack {
View1().glassEffect()
View2().glassEffect()
}
// Mix variants
.glassEffect(.regular)
.glassEffect(.clear)
// Tint everything
.glassEffect(.regular.tint(.blue))
// Multiple glass without container
HStack {
Button("A") { }.glassEffect()
Button("B") { }.glassEffect()
}
API Quick Reference
// Basic
.glassEffect()
.glassEffect(.regular)
.glassEffect(.clear)
// With shape
.glassEffect(.regular, in: .capsule)
.glassEffect(.regular, in: .circle)
// Modifiers
.glassEffect(.regular.tint(.blue))
.glassEffect(.regular.interactive())
// Container
GlassEffectContainer(spacing: 30) { }
// Morphing
.glassEffectID("id", in: namespace)
// Button styles
.buttonStyle(.glass)
.buttonStyle(.glassProminent)
Conclusion
Remember the core principles:
- Navigation layer only - Never content
- Never stack glass on glass
- Use GlassEffectContainer for multiple elements
- Tint selectively - Primary actions only
- Trust the system - Accessibility is automatic
Official Resources:


Top comments (0)