Bring SwiftUI’s iOS 26 glass APIs to earlier deployments with lightweight shims—keep your UI consistent on iOS 18+, 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 isn't a pixel-perfect recreation; it offers compatibility layers and API conveniences so your code paths stay unified on older systems, then quietly defers to Apple’s implementation on platforms that ship it.
- Glass for every surface – Apply
universalGlassEffectto any view, shape, or customUniversalGlassconfiguration. Liquid accents, tinting, and interactivity - Buttons that feel native –
.universalGlass()and.universalGlassProminent()mirror the future SwiftUI button styles, including a custom material fallback that respects tint, control size, and press animations - Containers & morphing – Drop content into
UniversalGlassEffectContainerand use union/ID helpers to bridge to SwiftUI’s morphing APIs - Backports when you want them – Import the optional
UniversalGlassBackportstarget to get.glass,.glassProminent, and.glassEffect. They’re convenience shims that forward to the true SwiftUI APIs on iOS 26. - Modular design – Glass effects, button styles, transitions, and previews live in focused files
- On pre-OS 26 systems the fallback
UniversalGlassEffectContainerignores the publicspacingparameter; a fix is planned.
Add UniversalGlass to your Package.swift dependencies:
.package(url: "https://github.com/Aeastr/UniversalGlass.git", branch: "main")Then add the target to any product that needs it:
dependencies: [
.product(name: "UniversalGlass", package: "UniversalGlass")
]Need the optional shorthand APIs (.glass, .glassEffect, etc.)? Add the backport module too:
dependencies: [
.product(name: "UniversalGlass", package: "UniversalGlass"),
.product(name: "UniversalGlassBackports", package: "UniversalGlass")
]import UniversalGlass
// import UniversalGlassBackports if you prefer the native names
struct Card: View {
var body: some View {
Text("Cosmic Glass")
.font(.headline)
.padding(.horizontal, 36)
.padding(.vertical, 18)
.universalGlassEffect(.regular.tint(.purple))
.padding()
.background(
LinearGradient(
colors: [.black, .purple.opacity(0.6)],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
)
}
}Need a custom shape?
Circle()
.frame(width: 120, height: 120)
.universalGlassEffect(in: Circle())Preset helpers roughly match Apple’s tiers:
.ultraThin→ clear glass.thin→ clear glass with a light tint from the system background.regular→ regular liquid glass.thick→ regular glass with a stronger background tint.ultraThick→ regular glass with a deep background tint
Button("Join Beta") {
// action
}
.tint(.pink)
.controlSize(.large)
.buttonStyle(
.universalGlassProminent()
)Legacy fallbacks apply a capsule material with tint-aware highlights, drop shadow, and press animations
@Namespace private var glassNamespace
UniversalGlassEffectContainer(spacing: 24) {
HStack(spacing: 16) {
AvatarView()
.universalGlassEffect()
.universalGlassEffectUnion(id: "profile", namespace: glassNamespace)
DetailsView()
.universalGlassEffect()
.universalGlassEffectTransition(.matchedGeometry)
}
}When a runtime supports GlassEffectContainer or glassEffectTransition, UniversalGlass forwards automatically; otherwise it falls back to material and blur transitions.
@State private var showSettings = false
VStack {
if showSettings {
SettingsPanel()
.transition(.blur)
}
Toggle("Show Settings", isOn: $showSettings)
.universalGlassButtonStyle()
}
.animation(.easeInOut(duration: 0.3), value: showSettings)Need a custom feel? Use AnyTransition.blur(intensity:scale:) and apply the timing via
.animation on the parent view. When you explicitly need the back-port animation used
under the hood, reach for .fallbackBlur:
// Force the same animation UniversalGlass uses on older platforms.
SettingsPanel()
.transition(.fallbackBlur)
// Dial in a custom blend while still supplying your own animation.
SettingsPanel()
.transition(.blur(intensity: 8, scale: 0.85))
.animation(.spring(response: 0.5, dampingFraction: 0.8), value: showSettings)import UniversalGlassBackports
Button("RSVP") {}
.buttonStyle(.glassProminent)
CardView()
.glassEffect(.regular.tint(.mint))SwiftUI’s real implementations automatically replace these extensions when available.
Contributions are welcome—bug reports, feature proposals, docs, or polish. Before submitting a PR, please:
- 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.
If UniversalGlass helps your project, consider starring the repo or sharing it with your team. Feedback, ideas, and issues are all appreciated!
Built with 🍏💦🔍 by Aether