Writing/Swift
Liquid Glass in React Native
Apple's native effect on iOS 26, a fallback everywhere else, and how to make one component decide which is which.
iOS 26 added Liquid Glass and it looks superb. If you build with React Native there is a catch: you cannot use it on older iPhones, and you cannot use it on Android at all. So I built my own, and the interesting part turned out not to be the effect. It was who decides which one you get.
The decision belongs in the component
The obvious approach is two components and a version check in the app. That pushes the problem onto every author who uses the library, and it guarantees the two paths drift apart the first time one of them gets a fix and the other does not.
So the library never asks what version of iOS it is running on. It asks whether Apple's glass module loaded at all, and then whether that module reports its own API as available. A package can be installed on a phone too old to run it, and a version number would have answered the wrong question in both directions.
let resolved: GlassEffectModule | null | undefined; if (resolved !== undefined) return resolved;resolved = null;if (Platform.OS !== 'ios') return resolved; try { const mod = require('expo-glass-effect'); // installed is not the same as supported if (mod.isGlassEffectAPIAvailable?.() && mod.GlassView) resolved = mod;} catch { resolved = null; }The cache has three states rather than two, which is the detail that matters. Undefined means nobody has asked yet. Null means the question was asked and the answer was no. Without that distinction a device without the module retries a failing require on every render of every glass surface on screen.
Being honest about the fallback
“You cannot recreate it. The native effect refracts what is behind it in real time, and JavaScript cannot. So do not pretend.”
The real effect samples and refracts whatever is behind it, live. There is no way to do that in JavaScript and SVG, and any claim otherwise is marketing. What you can get very close to is the blur, the edge light, and the way the thing moves. So that is what the fallback goes after, and the library says plainly that it is a reconstruction.
The motion is the part I am happiest with. The pill on the segmented control stretches and settles close enough to the real one that side by side you have to look for the difference. There is no native slider yet, so that control always runs my version, which is a useful reminder that the tier is per component and not per device.
What the whole thing is really about
One line in the app, the best available experience on whatever is holding it, and no configuration. If the native side is missing the component degrades instead of throwing. The app author writes the same line either way, and keeping that line identical is the entire product.
