Aeastr/UniversalGlass
Bring SwiftUI’s iOS 26 glass APIs to earlier deployments with lightweight shims—keep your UI consistent on iOS 18+, and automatically defer to the real implementations wherever they exist.
Overview
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
Installation
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.) |
Usage
Glass Effects
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 Styles
Button("Join Beta") { }
.buttonStyle(.universalGlassProminent())
.tint(.pink)See Button Styles for routing behaviour and material fallback details.
Effect Containers
@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.
Backports
import UniversalGlassBackports
Button("RSVP") {}
.buttonStyle(.glassProminent)See Backports for the full API surface.
Customization
Tinting and Interactivity
.universalGlassEffect(.regular.tint(.cyan))
.universalGlassEffect(.regular.interactive())Fallback Materials
Override what renders on older OS versions:
.universalGlassEffect(.regular.fallback(material: .thin))
.universalGlassEffect(.thick.fallback(material: .regular, tint: .blue.opacity(0.2)))Shadows
.universalGlassEffect(.regular.shadow(UniversalGlassShadow(color: .red, radius: 12)))
.universalGlassEffect(.regular.shadow(.none))Global Rendering Mode
Force all effects in a hierarchy to use a specific renderer:
MyApp()
.universalGlassRenderingMode(.material) // Force fallbackChaining
All 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.
How It Works
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.
Contributing
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
License
MIT. See LICENSE for details.
Package Metadata
Repository: Aeastr/UniversalGlass
Stars: 158
Forks: 8
Open issues: 2
Default branch: main
Primary language: swift
README: README.md