nitro-blur
v0.1.0
Published
Backdrop blur for React Native with native iOS effects and one Android GPU renderer
Readme
Nitro Blur
Backdrop blur for React Native. Place a BlurView over your content and it blurs what's behind it. Text and controls inside the view stay sharp. You don't need a target ref or provider.
iOS uses native blur materials. Android uses a GPU renderer with a choice of detail levels through the quality prop.
Install
bun add nitro-blur react-native-nitro-modules
cd ios && pod installRebuild your app after installing the native modules.
Requirements
| Dependency | Requirement | | --- | --- | | React Native | 0.78+ with the New Architecture | | React | 19+ | | Nitro Modules | 0.35.9 or later in the 0.35.x series | | Android | API 24+ with a hardware-accelerated window | | iOS | Your React Native version's minimum deployment target |
This is an early release. The example has been tested with React Native 0.85.3, an iOS simulator, and a Samsung SM-A146P running Android 15. The full declared compatibility range still needs testing.
Usage
import { ScrollView, Text, View } from 'react-native'
import { BlurView } from 'nitro-blur'
export function Screen() {
return (
<View style={{ flex: 1 }}>
<ScrollView>{/* Your content */}</ScrollView>
<BlurView
intensity={50}
quality="high"
tint="light"
style={{ position: 'absolute', top: 0, left: 0, right: 0, padding: 24 }}
>
<Text>This text stays sharp.</Text>
</BlurView>
</View>
)
}Place the content behind the blur in the view's drawing order. In this example, the ScrollView comes first and the blur sits above it as an absolute overlay.
Layout and touches
BlurView accepts normal View props. Use style to set its size, position, padding, and borderRadius. The container clips both the blur and its children to its bounds, including rounded corners.
Use pointerEvents="none" for a decorative overlay that lets all touches through. Use pointerEvents="box-none" if the blur contains controls that should remain interactive while touches outside those controls pass through.
Props
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| intensity | number | 50 | Blur strength from 0 to 100. |
| quality | BlurQuality | 'medium' | Android detail preset. iOS ignores it. |
| tint | BlurTint | 'default' | Light, dark, or system appearance. |
| onError | (error: Error) => void | Unset | Handles Android capture or rendering failures. |
The package exports BlurViewProps, BlurQuality, and BlurTint for TypeScript users.
Intensity
intensity controls strength on both platforms. At 0, the blur layer is transparent and its children remain visible. Values outside 0 to 100, NaN, and infinity throw a RangeError during render.
The same value can look different across platforms. iOS interpolates a native material effect. Android maps intensity to a filter radius of up to 40 dp. Treat intensity as effect strength, not a Gaussian radius shared by both platforms.
Android quality
| Value | Working resolution | Tradeoff |
| --- | --- | --- |
| 'low' | Up to 8× downsampling per dimension | Less rendering work, less detail. |
| 'medium' | Up to 4× downsampling per dimension | The default balance of detail and cost. |
| 'high' | Up to 2× downsampling per dimension | More detail, more rendering work. |
All presets target the same blur strength. They change the working resolution and filter approximation, so fine detail can differ. Subtle blur uses less downsampling.
Quality does not change the update frequency or select another renderer. iOS ignores this prop.
Tint and accessibility
tint accepts 'light', 'dark', or 'default'. The default follows the system's light or dark appearance.
iOS respects Reduce Transparency. When that setting is enabled and intensity is above zero, the view shows a solid material color. Android's quality prop has no effect on this behavior.
Video on Android
The video player must render into a TextureView for its output to appear behind the blur. A SurfaceView renders into a separate layer that Nitro Blur cannot capture. Protected video cannot be captured either.
The example uses react-native-video 6.15.0 with ViewType.TEXTURE. Other players need their equivalent setting. See the example app for the player setup and its version-specific notes.
Error handling
import { useState } from 'react'
import { Text } from 'react-native'
import { BlurView } from 'nitro-blur'
export function Panel() {
const [error, setError] = useState<Error>()
if (error) return <Text>{error.message}</Text>
return <BlurView style={{ height: 120 }} onError={setError} />
}An Android capture or rendering failure stops the renderer and makes the blur layer transparent. Its children remain visible. Without onError, the error goes to React Native's exception handler.
Unmount and remount the view to create a fresh renderer after a failure. Unmounting releases the native resources. There is no software or alternate blur backend.
onError handles native Android failures. Invalid intensity values throw during React rendering and do not reach this callback.
Android performance and limits
Each blur owns a capture session and adds capture and composition work. Large blur areas and overlapping layers cost more. Start with the default quality and measure on your target devices before raising it.
The renderer allows one capture in flight at a time. It skips capture when the view is hidden, offscreen, or at zero intensity. Visible views capture on draw updates, even when the source content has not changed.
- Capture and output are asynchronous. Blur can trail the source during fast scrolling, especially with overlapping layers. Same-frame delivery is not guaranteed.
- Capture covers ordinary React Native views in the same window. Separate
SurfaceViewlayers, other windows, and protected content are outside its reach. This affects some video, camera, and map views. - Custom native drawing and some composition effects are not fully reproduced. The renderer details below describe these cases.
Two overlapping medium-quality blurs over video have been profiled on the Samsung SM-A146P. That result does not establish performance on other devices or at high quality.
The UI thread replays the part of the view hierarchy drawn before the blur into Surface.lockHardwareCanvas(). A SurfaceTexture passes that image to a GL thread, which applies Dual Kawase blur and presents it in a TextureView. This avoids per-frame CPU pixel readback. The renderer uses no RenderScript, RenderEffect, PixelCopy, or private Android APIs.
Capture includes surrounding pixels for the filter and handles ordinary view transforms, scroll offsets, React Native zIndex, and elevation ordering. An upper blur can sample an already-presented lower blur. Capture does not hide or change source views.
Ancestors on the path to the blur replay their backgrounds and children. Sibling subtrees use their normal draw implementation. Custom native ViewGroup.onDraw content, custom child drawing order, animated legacy view transforms, outline clipping, and render effects are not fully reproduced. Elevation order is respected, but shadow fidelity, group opacity, blend modes, and nested clipping need device validation.
The GL thread creates and releases the capture surface, shaders, and render targets. The UI thread borrows the capture surface while a frame is in flight. Closing a session prevents further capture and queues cleanup behind pending render work. The output texture stays with TextureView until its destruction callback transfers ownership. The session releases that texture once GL cleanup finishes. Unmounting also releases the GL context and shuts down the worker thread.
Example and development
The example app includes video and scrolling backdrops, overlapping blur layers, and controls for intensity, tint, Android quality, and mounting the view.
# From the repo root
bun install
bun run blur:specs
bun run example:androidFor iOS, install the example's pods in apps/example/ios, then run bun run example:ios from the repo root.
blur:specs runs Nitrogen directly. Generated bindings ship in the package. Edit src/specs and regenerate instead of editing nitrogen/generated.
Report issues at nitroverse/issues. Include your device, OS, React Native version, and a small example that reproduces the problem.
License
MIT. See LICENSE.
