SwiftUI Liquid Glass and Accessibility - Part 2
This is Part 2 covering VoiceOver support and testing. Part 1 covered Reduce Motion and Dynamic Type.
VoiceOver Labels, Hints, and Grouping
VoiceOver reads UI elements aloud for blind and low-vision users. SwiftUI provides default labels for text and images, but custom views need explicit accessibility annotations. Without proper labels, VoiceOver reads raw view hierarchies ("Image, Text, Text, Text") instead of meaningful descriptions.
Combining elements: Group related views so VoiceOver reads them as one logical element:
HStack {
Image(systemName: "photo")
.resizable()
.frame(width: 60, height: 60)
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text("\(item.quantity) items")
.font(.subheadline)
if let notes = item.notes {
Text(notes)
.font(.caption)
}
}
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(item.name), \(item.quantity) items")
.accessibilityHint(item.notes ?? "No notes")
.accessibilityValue(item.condition?.rawValue ?? "")
.accessibilityElement(children: .combine) merges child elements into one. .accessibilityLabel() is the primary text VoiceOver reads immediately—keep it short. .accessibilityHint() is secondary context read after a delay. .accessibilityValue() holds current state.
Stat Card with Icon:
VStack {
Image(systemName: "star.fill")
.font(.title)
Text("\(count)")
.font(.title2)
.fontWeight(.bold)
Text(label)
.font(.caption)
}
.accessibilityElement(children: .combine)
.accessibilityLabel("\(label): \(count)")
Without .accessibilityElement(children: .combine), VoiceOver reads "Star, 42, Favorites". With grouping: "Favorites: 42".
iOS 26 Liquid Glass Defaults
.glassEffect() automatically respects Reduce Motion and Reduce Transparency system settings. Liquid Glass materials adapt opacity and blur intensity based on accessibility preferences. GlassEffectContainer groups glass views for shared rendering and better performance. .interactive() makes glass respond to touch and focus with subtle lighting—disabled when Reduce Motion is on.
Apply .glassEffect() to floating elements (toolbars, tab bars, cards). Avoid placing solid fills behind glass views. Don't combine .glassEffect() with manual .blur() or .opacity() modifiers. Use GlassEffectContainer when multiple glass elements share the same space.
Testing Your Implementation
Reduce Motion: Settings -> Accessibility -> Motion -> Reduce Motion -> ON. Verify .glassEffect() is replaced with static materials. Check that parallax and refraction animations are disabled.
Reduce Transparency: Settings -> Accessibility -> Display & Text Size -> Reduce Transparency -> ON. Glass elements should become more opaque and frosty. Content behind glass should be more obscured for better contrast.
Dynamic Type: Settings -> Accessibility -> Display & Text Size -> Larger Text. Drag the slider to maximum. Check that text on glass backgrounds stays readable and layouts don't break.
VoiceOver: Settings -> Accessibility -> VoiceOver -> Enable. Navigate through glass UI elements. Listen for meaningful descriptions, not just "Glass effect, Button".
Combined testing: Enable Reduce Motion + Reduce Transparency + Dark Mode together for the most accessibility-friendly configuration.
What I Learned
Accessibility isn't an afterthought in Liquid Glass—Apple baked it in from the start. But you have to respect it in custom implementations.
The @Environment(\.accessibilityReduceMotion) pattern is straightforward. When Reduce Motion is on, I replace .glassEffect() with .ultraThinMaterial for a static blur without morphing animations. VoiceOver grouping with .accessibilityElement(children: .combine) solved screen readers treating each glass element separately. Dynamic Type worked automatically once I switched from fixed font sizes to SwiftUI's built-in text styles.
Liquid Glass looks incredible but is visually intense. Some users find it distracting or hard to read. Testing with Reduce Motion + Reduce Transparency showed me how much the system adapts—glass becomes more opaque, animations stop, and the UI feels closer to iOS 25's flat design.
What I'd do differently next time: check accessibilityReduceMotion and fall back to .ultraThinMaterial instead of .glassEffect(), use SwiftUI text styles with dynamicTypeSize ranges for text on glass backgrounds, group compound glass views with accessibilityElement(children: .combine), test with both Reduce Motion and Reduce Transparency enabled, and never manually combine .glassEffect() with .blur() or .opacity().
Tip
Test with both Reduce Motion and Reduce Transparency enabled simultaneously. That combination reveals the most about how your glass UI degrades.
The hard part is not the API itself but remembering to test with those settings enabled.
Related posts:
- Part 1: Reduce Motion and Dynamic Type - Foundation accessibility techniques for Liquid Glass
- Understanding SwiftUI's TabView - Apply these accessibility patterns to tab views
- SwiftUI Bottom Toolbar and Placement API - Make toolbars accessible with proper placement