react-native-glass-wrap
v0.1.0
Published
Wrap any React Native content in a real, OS-rendered Liquid Glass surface (iOS 26 UIGlassEffect) with graceful fallbacks.
Maintainers
Readme
react-native-glass-wrap
Wrap any React Native content in a real Liquid Glass surface — rendered by the OS, not simulated in JS.
<GlassView style={{ borderRadius: 24, padding: 16 }}>
<Text>Anything you put here sits on real glass.</Text>
</GlassView>Why native rendering matters
Liquid Glass is not a blur plus a translucent fill. It refracts what is behind it,
reflects the environment, adapts its own legibility to the content underneath, and
shimmers under touch. None of that is reachable from JavaScript. This library hands
the job to UIGlassEffect inside a UIVisualEffectView and gets out of the way.
If you only need something frosted and you do not care that it is approximate,
a View with rgba(255,255,255,0.8) will do. This library is for when you want the
real material.
Install
npm install react-native-glass-wrap
cd ios && pod installRequirements:
| | |
|---|---|
| React Native | 0.80+, new architecture (Fabric) |
| Build toolchain | Xcode 26+ — the iOS 26 SDK provides UIGlassEffect |
| Deployment target | iOS 16+. Older OS versions get a UIBlurEffect fallback |
| Android / web | JS fallback surface (see Android) |
You can keep a low deployment target. The SDK you build against is what matters; devices below iOS 26 fall back automatically at runtime.
One thing to check before you blame this library
Xcode 26 is required for Liquid Glass, and the iOS 26 SDK refuses to launch an app that has not adopted the UIKit scene-based life cycle:
Application failed to launch: UIScene life cycle is required for apps built
with this SDK.The React Native app template still ships the pre-scene AppDelegate. It compiles
without a warning and then crashes the moment you open the app — which looks exactly
like a broken native module. If you hit this, migrate your AppDelegate and add
UIApplicationSceneManifest to Info.plist;
example/ios/AppDelegate.swift and
example/README.md have a working version and the plist
snippet. Nothing about it is specific to this library.
Components
GlassView
A container. Put whatever you like inside it.
import { GlassView } from 'react-native-glass-wrap';
<GlassView
effect="regular"
tintColor="#f07b3f"
style={{ borderRadius: 24, padding: 18 }}
>
<Text style={{ color: '#fff' }}>Hello</Text>
</GlassView>| Prop | Type | Default | Notes |
|---|---|---|---|
| effect | 'regular' \| 'clear' \| 'none' | 'regular' | clear is thinner and needs high-contrast content behind it. none removes the material — useful for animating glass away |
| tintColor | ColorValue | – | Vibrant tint baked into the material by the OS |
| interactive | boolean | false | Enables the OS grow-and-shimmer touch response. Use GlassButton instead of wiring this by hand |
| colorScheme | 'system' \| 'light' \| 'dark' | 'system' | Forces the material to resolve light or dark |
| cornerStyle | 'fixed' \| 'capsule' | 'fixed' | fixed honours borderRadius from style; capsule derives the radius from height |
| animated | boolean | true | Animate transitions when the material changes |
| animationDuration | number (ms) | – | Omit to use the system spring |
| legacyBlurFallback | boolean | true | On iOS 16–25, render a UIBlurEffect material instead of nothing |
| fallbackStyle | ViewStyle | – | Extra style applied only on platforms without native glass |
| fallbackBlurIntensity | number (0–100) | 40 | Passed to expo-blur / @react-native-community/blur if installed |
Plus every standard ViewProps.
Corner radius goes in
style, not in a prop. Internally the radius is read from Fabric's resolved border metrics and pushed through iOS 26'scornerConfiguration, which is the only path that makes the material refract correctly around its own edge. Settinglayer.cornerRadiuson glass is silently ignored by UIKit.
GlassButton
Pressable on the outside, glass on the inside, interactive on by default.
<GlassButton style={{ paddingHorizontal: 18, paddingVertical: 12 }} onPress={submit}>
<Text style={{ color: '#fff', fontWeight: '600' }}>Continue</Text>
</GlassButton>Takes all Pressable props, plus the GlassView material props, plus:
| Prop | Type | Default |
|---|---|---|
| containerStyle | ViewStyle | – | Style for the outer pressable (margins, alignment) |
| pressedScale | number | 0.97 | Set to 1 to disable — iOS 26 already animates interactive glass |
| cornerStyle | – | 'capsule' | Buttons default to capsule, unlike GlassView |
The gesture deliberately lives outside the glass. If you nest it the other way
round, UIKit's interactive glass swallows the touch and onPress never fires.
GlassContainer
Lets iOS blend and morph sibling glass views into each other.
<GlassContainer spacing={24} style={{ flexDirection: 'row', gap: 8 }}>
<GlassButton onPress={...}><Text>Home</Text></GlassButton>
<GlassButton onPress={...}><Text>Search</Text></GlassButton>
</GlassContainer>As two nested glass views come within spacing points of each other, UIKit merges
the materials and morphs the shapes. Containment is the entire mechanism — there is
no id, no registration, no matchedGeometryEffect equivalent to wire up.
Below iOS 26 this degrades to a plain View and each child keeps its own blur.
Capability flags
import {
isLiquidGlassSupported,
isNativeBlurSupported,
hasFallbackBlurLibrary,
} from 'react-native-glass-wrap';Synchronous constants, safe to read at module scope. isLiquidGlassSupported is
stricter than an OS version check — it is also false when:
- the binary was built against an SDK older than iOS 26,
- the host app set
UIDesignRequiresCompatibilityinInfo.plist(which opts the entire app out of the iOS 26 design system), - the pod was never installed, so nothing native is linked at all.
Use it to branch layout, not to decide whether to render GlassView — the component
already falls back on its own.
Android and other platforms
Android has no public API for blurring arbitrary view backdrops. RenderEffect
blurs a view's own content, and setBlurBehindRadius only applies at the window
level. Any library claiming in-app backdrop blur on Android is either snapshotting
the hierarchy every frame or shipping something that only works on one OEM.
So rather than ship native Android code that fakes it, GlassView renders a
carefully-tuned translucent surface: high alpha (0.8 regular / 0.62 clear) so text
stays readable without a blur behind it, a hairline highlight for the glass edge, and
your tintColor as a low-opacity overlay.
If you do want real blur on Android, install either optional peer and the fallback picks it up automatically — no configuration:
npm install expo-blur
# or
npm install @react-native-community/blurhasFallbackBlurLibrary tells you whether one was found.
Design notes and gotchas
Glass needs something behind it. On a flat background it looks like a slightly tinted rectangle, and reviewers conclude the library is broken. Put an image, a gradient, or scrolling content behind it.
Let content scroll underneath. A floating glass bar over a ScrollView is the
canonical use. Add paddingBottom to the scroll content — roughly bar height plus
50 — so the last row is not hidden.
You do not control the tint on iOS 26 the way you did on iOS 18. The OS decides
how much of your tintColor survives, based on what is behind the glass. If your
brand colour must be exact, the glass is the wrong surface for it.
Views are not recycled. Both native components return NO from
shouldBeRecycled. A recycled glass view arrives with a material configured for
different props, which shows up as stale glass in long lists. The cost is one extra
view allocation on mount.
Reduce Transparency. When the accessibility setting is on, iOS flattens the material itself. Do not add your own handling; you will double up.
Under the hood
GlassWrapView (ObjC++, RCTViewComponentView)
└─ self.contentView = GlassWrapViewImpl (Swift, UIVisualEffectView)
└─ .contentView.subviews = your RN childrenChildren have to land in the UIVisualEffectView's own contentView. Adding them
to the effect view directly puts them outside the vibrancy pipeline — no automatic
legibility adaptation, and Apple documents it as undefined behaviour.
The ObjC++ layer is not optional decoration. Swift cannot conform to
RCTComponentViewProtocol: it returns a C++ struct by value and
RCTViewComponentView declares C++ ivars, neither of which Swift's Objective-C
importer can parse. Every Fabric view library — Expo Modules and Nitro included —
has an ObjC++ shim somewhere; ours is just visible.
One subtlety worth knowing if you read the source: UIGlassEffect disables
userInteractionEnabled on its internal content view when no subviews exist at the
moment the effect is applied. Under Fabric, children mount after first layout, so
we re-enable it explicitly. Without that line, nothing inside glass is tappable.
Contributing
See CONTRIBUTING.md.
Acknowledgements
The Fabric view structure follows patterns established by
@callstack/liquid-glass and
react-native-bottom-tabs.
License
MIT © Steve Tran
