Bring SwiftUI's iOS 26 glass APIs to earlier deployments with lightweight shims—keep your UI consistent on iOS 17+, yet automatically defer to the real implementations wherever they exist.
OS 26 introduces new SwiftUI glass APIs, but these only ship on the latest platforms. UniversalGlass offers compatibility layers so your code stays unified on older systems, then quietly defers to Apple's implementation where available.
- Glass for every surface – Apply
universalGlassEffectto any view with tinting and interactivity - Native-feeling buttons –
.universalGlass()and.universalGlassProminent()button styles - Containers & morphing –
UniversalGlassEffectContainerwith union/ID helpers for glass grouping - Backports – Optional
UniversalGlassBackportstarget for.glassand.glassEffectsyntax
dependencies: [
.package(url: "https://github.com/Aeastr/UniversalGlass.git", branch: "main")
]import UniversalGlass| Target | Description |
|---|---|
UniversalGlass |
Main module with glass effects, button styles, and containers |
UniversalGlassBackports |
Optional shorthand APIs (.glass, .glassEffect, etc.) |
Text("Hello")
.universalGlassEffect(.regular.tint(.purple))With a custom shape:
Circle()
.frame(width: 120, height: 120)
.universalGlassEffect(in: Circle())See Effects for configurations, fallback customization, and transitions.
Button("Join Beta") { }
.buttonStyle(.universalGlassProminent())
.tint(.pink)See Button Styles for routing behaviour and material fallback details.
@Namespace private var ns
UniversalGlassEffectContainer {
HStack {
AvatarView()
.universalGlassEffect()
.universalGlassEffectUnion(id: "profile", namespace: ns)
DetailsView()
.universalGlassEffect()
.universalGlassEffectUnion(id: "profile", namespace: ns)
}
}See Containers for grouping behaviour and Container Internals for the fallback pipeline.
import UniversalGlassBackports
Button("RSVP") {}
.buttonStyle(.glassProminent)See Backports for the full API surface.
.universalGlassEffect(.regular.tint(.cyan))
.universalGlassEffect(.regular.interactive())Override what renders on older OS versions:
.universalGlassEffect(.regular.fallback(material: .thin))
.universalGlassEffect(.thick.fallback(material: .regular, tint: .blue.opacity(0.2))).universalGlassEffect(.regular.shadow(UniversalGlassShadow(color: .red, radius: 12)))
.universalGlassEffect(.regular.shadow(.none))Force all effects in a hierarchy to use a specific renderer:
MyApp()
.universalGlassRenderingMode(.material) // Force fallbackAll modifiers chain:
.universalGlassEffect(
.ultraThick
.fallback(material: .thin, tint: .cyan.opacity(0.3))
.shadow(UniversalGlassShadow(color: .blue, radius: 16))
.tint(.purple)
.interactive()
)See Effects for the full configuration API.
UniversalGlass uses runtime availability checks to route calls to native SwiftUI APIs on OS 26+ or fall back to material-based approximations on earlier systems. The fallback renderer:
- Registers effects as participants via anchor preferences
- Groups views by union keys or effect IDs
- Draws composite material overlays that respect shapes and transitions
For technical deep dives, see the docs:
- Effects – Runtime routing and fallback rendering
- Button Styles – Primitive style architecture
- Containers – Union grouping logic
- Container Internals – Full fallback pipeline
Known limitation: On pre-OS 26 systems, the fallback container ignores the spacing parameter.
Contributions welcome. Before submitting a PR:
- Create an issue outlining the change (optional for small fixes)
- Follow the existing Swift formatting and file organisation
- Ensure
swift buildsucceeds and add previews/tests where relevant
MIT. See LICENSE for details.
