npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-android-liquid-glass

v0.2.1

Published

iOS 26–style Liquid Glass for React Native on Android — AGSL refraction, glassmorphism, chromatic dispersion, and sensor highlights. Safe cross-platform fallbacks.

Readme

react-native-android-liquid-glass

npm version npm downloads license platform architecture

Liquid Glass / glassmorphism for React Native on Android — real AGSL refraction (not a flat blur), chromatic dispersion, adaptive tint, and tilt-driven specular highlights. Built as a Fabric bridge over QWEA0/Liquid-Glass-Android, so you can bring an iOS 26 Liquid Glass look to Android View UIs.

Also searched as: frosted glass, glass blur, backdrop blur, UIGlassEffect for Android, expo-glass-effect Android alternative.

The effect is Android-only and New Architecture only. The package is safe to import and render everywhere. On iOS, web, macOS, Windows and in Jest the components degrade to a styled View — a dimming scrim, a correctly clamped corner radius and an edge hairline — rather than throwing. See Cross-platform behaviour. For native glass on iOS 26, pass your own renderFallback backed by UIGlassEffect / expo-glass-effect.

Why this library

React Native has no built-in Liquid Glass. Most “glass” packages are either iOS-only (UIGlassEffect / expo-glass-effect) or a simple frosted blur. This package is for apps that need the Android side of that material:

| You want… | This package | | --- | --- | | iOS 26–style liquid glass on Android | ✅ AGSL SDF lens (API 33+) | | Real refraction / dispersion, not only blur | ✅ lens + classic pipelines | | Glass that works in lists, nav bars, tab bars | ✅ recipes + ScrollEdgeBlurView | | Safe import on iOS / web / Jest | ✅ degraded View + renderFallback | | Expo Go | ❌ needs a dev build / prebuild (native module) |

npm install react-native-android-liquid-glass
# or
yarn add react-native-android-liquid-glass

Features

  • Real AGSL lens on API 33+ — SDF refraction, per-channel dispersion, normal-lit specular highlight, with an automatic CPU/GPU blur fallback down to API 24.
  • 42 props across both pipelines, fully typed, with upstream's defaults spelled out so JS and native never drift.
  • Liquid shape merging — two glass bodies that smooth-min blend and cling to each other like mercury, driven through Fabric commands.
  • Adaptive tint that samples backdrop luminance and tells you when to flip your foreground colour.
  • Gravity-driven specular — the rim catches light as the device tilts.
  • ScrollEdgeBlurView — progressive edge blur for content scrolling under a translucent header.
  • Frame-time diagnostics through a natively throttled event, so you can tune on a real mid-range device instead of guessing.
  • No runtime dependencies beyond React Native itself.

Requirements

| | | | --- | --- | | React Native | 0.85, New Architecture (Fabric) | | minSdk | 24 — the AGSL lens pipeline needs API 33+, below that it falls back | | compileSdk | 36 | | ABIs | arm64-v8a, armeabi-v7a only (upstream ships no x86 .so) | | Upstream | com.github.QWEA0:liquidglass:v2.0.0 |

Installation

npm install react-native-android-liquid-glass

The upstream library is published on JitPack, and android/build.gradle declares that repository itself. If your app sets RepositoriesMode.FAIL_ON_PROJECT_REPOS in android/settings.gradle, that declaration is ignored and you have to add it in the app instead:

dependencyResolutionManagement {
  repositories {
    google()
    mavenCentral()
    maven { url "https://jitpack.io" }
  }
}

To pin a different upstream version, set liquidGlassVersion in your app's android/build.gradle:

buildscript {
  ext {
    liquidGlassVersion = "v2.0.0"
  }
}

The coordinate in the upstream README (com.github.QWEA0.Liquid-Glass-Android:liquidglass) does not resolve — the module overrides its own groupId. The working one is com.github.QWEA0:liquidglass, which is what this package uses.

Quick start

import { Text, View } from 'react-native';
import { LiquidGlassAndroidView } from 'react-native-android-liquid-glass';

<View collapsable={false} style={{ flex: 1 }}>
  {/* whatever should show through the glass */}
  <YourContent />

  <LiquidGlassAndroidView
    style={{ width: 260, height: 140 }}
    cornerRadius={28}
    material="regular"
    refractionHeight={66}
    enableDynamicBackground
  >
    <Text>Liquid Glass</Text>
  </LiquidGlassAndroidView>
</View>;

The component is a FrameLayout natively, so children render on top of the glass — put your content inside it.

Three rules that decide whether this works

Almost every "the effect doesn't show up" case is one of these.

1. The backdrop must be in the glass's immediate parent

Upstream captures the backdrop with view.parent as? View. It draws that one parent's subtree, and nothing above it. So:

  • Content you want refracted has to be a sibling of the glass, under the same parent. A sibling of the parent is invisible to the capture.
  • Anything drawn after the glass in that same parent lands in the capture too. Put overlays and controls outside the parent, or they appear smeared inside the glass.
  • Add collapsable={false} to that parent. React Native flattens away views it considers redundant, which silently re-parents the glass and changes what it captures.

2. enableDynamicBackground={false} if nothing moves

This defaults to true, which is not upstream's field default. Nothing in React Native invalidates the glass when a sibling repaints, so with it off the backdrop is captured once and then frozen — scrolling content slides past while the glass keeps showing a stale image, which reads as a broken effect rather than a saving.

It is not free: upstream implements it by calling invalidate() at the end of every draw, so it is an unbounded redraw loop at display refresh rate plus a backdrop re-capture per frame, for as long as the view is mounted. Turn it off for genuinely static glass over static content, and reach for paused when a view stays mounted but stops being visible.

3. The backdrop needs high-frequency detail

Refraction bends the backdrop. A flat colour bent is still that flat colour, and a smooth gradient barely changes. Over solid backgrounds the effect is invisible and the library looks broken. The example app ships flat and gradient backdrops specifically so you can see this failure mode.

Props

Every dimension marked dp is converted to px natively. Props upstream declares as unitless floats — including displacementScale and dispersionThickness, which are map-space scalars rather than screen distances — are passed straight through.

Lens pipeline (API 33+)

| Prop | Type | Default | | --- | --- | --- | | material | 'regular' \| 'clear' | 'regular' | | cornerRadius | number (dp) | 999 (pill) | | bevelWidth | number (dp) | 14 | | refractionHeight | number (dp) | 66 | | dispersionStrength | number 01 | 0.1 | | enableSensorHighlight | boolean | false | | enableAdaptiveTint | boolean | false | | useShaderPipeline | boolean | true |

refractionHeight is the dominant knob — at 0 the glass flattens into a plain blur. material="clear" blurs far less and applies a fixed dimming layer instead of adaptive tint; it is for glass over photos and video.

Blur

| Prop | Type | Default | | --- | --- | --- | | enableDynamicBackground | boolean | true | | enableBackdropBlur | boolean | true | | paused | boolean | false | | layerType | 'hardware' \| 'none' | 'hardware' | | blurScale | number (unitless multiplier) | 0.0625 | | blurMethod | 'smart' \| 'boxBlur' \| 'boxBlurCpp' \| 'iirGaussian' \| 'iirGaussianNeon' \| 'downsample' \| 'box3' | 'smart' | | highQualityBlur | boolean | false | | downsampleScale | int, 2–3 | 2 | | globalDownsampleFactor | number 0.251 | 1 | | useHardwareBlurWhenPossible | boolean | true | | enableOptimizedCapture | boolean | false |

blurScale is a unitless multiplier: the effective radius is (overLight ? 12 : 4) + blurScale * 32 pixels. It is not @react-native-community/blur's 0–100 pixel radius — copying blurAmount={20} from that library gives a 644 px radius here, and __DEV__ warns about it. (blurAmount is the old name for this prop and still works for one minor version.)

downsampleScale is clamped by upstream to 2–3, so raising it further is not a tuning lever. Use globalDownsampleFactor instead.

paused stops the enableDynamicBackground redraw loop without unmounting — set it whenever the view is offscreen but still mounted, such as behind a pushed navigation screen or on an inactive tab.

Chromatic aberration

| Prop | Type | Default | | --- | --- | --- | | enableChromaticAberration | boolean | true | | aberrationIntensity | number | 2 | | aberrationDownsample | number 0.251 | 0.5 | | aberrationRedOffset | number | 0 | | aberrationGreenOffset | number | -0.05 | | aberrationBlueOffset | number | -0.1 | | aberrationUseBilinearInterpolation | boolean | true | | chromaticAberrationMode | 'auto' \| 'cpp' \| 'kotlin' | 'auto' |

Physical dispersion

| Prop | Type | Default | | --- | --- | --- | | enableChromaticDispersion | boolean | false | | dispersionThickness | number (unitless) | 100 | | dispersionFactor | number | 1.5 | | dispersionGain | number | 7 | | dispersionDownsample | number 0.251 | 0.5 |

The most expensive effect in the library. Measure it before shipping it, and never put it in a list.

Edges, shadow, tone

| Prop | Type | Default | | --- | --- | --- | | enableEdgeHighlight | boolean | true | | edgeHighlightBorderWidth | number (dp) | 0.5 | | edgeHighlightOpacity | number 0100 (percent) | 100 | | enableShadow | boolean | false | | saturation | number (percent, 100 = unchanged) | 140 | | overLight | boolean | false |

edgeHighlightOpacity and saturation are percentages, not the 01 React Native uses for opacity. Upstream's units are kept rather than silently rescaled.

Displacement & touch

| Prop | Type | Default | | --- | --- | --- | | displacementScale | number (unitless) | 70 | | displacementMode | 'standard' \| 'polar' \| 'prominent' | 'standard' | | elasticity | number | 0.15 |

Accessibility & diagnostics

| Prop | Type | Default | | --- | --- | --- | | accessibilityMode | 'auto' \| 'forceFull' \| 'forceOpaque' | 'auto' | | collectFrameStats | boolean | true | | frameStatsInterval | int, ms (0 = off) | 0 |

auto degrades to an opaque material when the system asks for reduced transparency or high-contrast text. forceFull overrides a user's explicit accessibility preference — reach for it only when your own layer already guarantees contrast.

Events

<LiquidGlassAndroidView
  enableAdaptiveTint
  onGlassAppearanceChange={(e) => setDark(!e.nativeEvent.isOverLight)}
  onPipelineReady={(e) => console.log(e.nativeEvent.deviceApiLevel)}
  frameStatsInterval={250}
  onFrameStats={(e) => setFps(e.nativeEvent.drawFps)}
/>

| Event | Payload | Notes | | --- | --- | --- | | onGlassAppearanceChange | { isOverLight } | Fires when backdrop luminance crosses the light/dark threshold, with hysteresis. Needs enableAdaptiveTint. | | onPipelineReady | { deviceApiLevel, supportsLensPipeline } | One-shot, after the first prop commit. | | onFrameStats | { drawFps, totalMs, captureMs, blurMs, effectMs, finalizeMs, effectName, blurRecomputed, effectRecomputed, processedWidth, processedHeight } | Requires collectFrameStats and frameStatsInterval > 0. |

onFrameStats is throttled natively. Upstream invokes its listener on every drawn frame; forwarding that across the bridge at 60fps would cost more than the effect, so frameStatsInterval={0} (the default) never attaches a JS-visible listener at all. It is a development HUD — ship it off.

supportsLensPipeline is a device capability check, not a promise about the frame you are looking at: useShaderPipeline={false} or an accessibility degradation still force the classic path on a capable device.

Imperative API

Shapes are commands, not props. Grab a ref:

import { useEffect, useRef } from 'react';
import type { LiquidGlassAndroidViewHandle } from 'react-native-android-liquid-glass';

const glass = useRef<LiquidGlassAndroidViewHandle>(null);

useEffect(() => {
  // Two glass bodies that smooth-min blend — close the gap and they merge.
  glass.current?.setPrimaryShape({
    x: 20, y: 40, width: 96, height: 96, cornerRadius: 48,
  });
  glass.current?.setSecondaryShape({
    x: 150, y: 40, width: 96, height: 96, cornerRadius: 48, smoothing: 16,
  });
}, []);

<LiquidGlassAndroidView ref={glass} style={{ width: 300, height: 190 }} />;

| Method | Notes | | --- | --- | | setPrimaryShape(shape) | Glass geometry inside the view, dp, relative to its top-left. Lens pipeline only. | | clearPrimaryShape() | Back to filling the whole view. | | setSecondaryShape(shape) | A second body, smooth-min blended with the first. | | clearSecondaryShape() | Remove it. | | refreshAccessibilityState() | Re-read system settings, e.g. from an AppState active handler after the user returns from Settings. |

Omitting cornerRadius reproduces upstream's defaults: the view's current cornerRadius for the primary shape, a pill for the secondary.

ScrollEdgeBlurView

The library's second component: a progressive blur pinned to one edge, so content dissolves as it scrolls under a header. It is a plain View, takes no children, and should be positioned absolutely.

import { ScrollEdgeBlurView } from 'react-native-android-liquid-glass';

<ScrollEdgeBlurView
  pointerEvents="none"
  style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 90 }}
  edge="top"
  maxBlurRadius={40}
/>;

| Prop | Type | Default | | --- | --- | --- | | edge | 'top' \| 'bottom' | 'top' | | maxBlurRadius | number, raw px | 40 |

maxBlurRadius is the one prop that is not converted from dp: upstream clamps it to 0–100 pixels, so converting would cut the usable range to about 33dp on a 3x screen. Scale it with PixelRatio.get() yourself if you want it density-independent.

Its handle exposes bindScrollView(ref) / unbindScrollView(). In practice ViewTreeObserver scroll callbacks are window-wide so it usually redraws either way, but binding documents the intent.

Cross-platform behaviour

Importing and rendering this package is safe on every platform. On anything other than Android both components render a degraded View:

| Android prop | Fallback mapping | | --- | --- | | cornerRadius | borderRadius, clamped to half the shorter side, borderCurve: 'continuous' | | material, overLight | a dimming backgroundColor scrim | | enableEdgeHighlight, edgeHighlightBorderWidth, edgeHighlightOpacity | borderWidth + borderColor | | enableShadow | shadow* | | everything blur/refraction/dispersion related | dropped |

The scrim is not decoration. On Android the regular material dims the backdrop, which is what makes white-on-glass text readable — a fully transparent fallback would put white text on a white background with no crash and no warning. The clamp is not polish either: an unclamped borderRadius: 999 renders as a square on iOS whenever a border is present.

onPipelineReady fires once on the fallback with supportsLensPipeline: false, so a capability gate resolves instead of hanging. The imperative handle is complete, with isSupported: false and commands that return false rather than throwing.

Branching without rendering

import { getGlassCapabilities, useGlassSupport } from 'react-native-android-liquid-glass';

const { supported, supportsLensPipeline } = useGlassSupport();

Supplying your own fallback

<LiquidGlassAndroidView
  style={styles.panel}
  renderFallback={({ style, children }) => (
    <GlassView style={style}>{children}</GlassView>   // e.g. expo-glass-effect
  )}
>
  <Text>Now playing</Text>
</LiquidGlassAndroidView>

This keeps expo-glass-effect out of this package's dependency graph entirely.

Error handling

Both components take an onError handler. It fires when a prop value is rejected, an enum string is unrecognised, a command throws natively, or a capability had to be demoted for the device:

<LiquidGlassAndroidView
  onError={({ nativeEvent: { code, prop, message, fatal } }) => {
    if (fatal) setUseGlass(false);
    else console.warn(`[glass] ${code} on ${prop}: ${message}`);
  }}
/>

Codes: NATIVE_BLUR_UNAVAILABLE, PIPELINE_DEGRADED, INVALID_ENUM, INVALID_VALUE, COMMAND_FAILED, BIND_FAILED, EVENT_DISPATCH_UNAVAILABLE. Most are fatal: false — the bridge recovered — but they always mean the view is not doing what the props asked for. Everything is logged to logcat under the LiquidGlassAndroidView tag even with no handler.

In __DEV__ a JS validation layer additionally warns (once per prop) about the things upstream would silently swallow: non-finite numbers, the percentage props that look like 0–1 ranges, values outside upstream's silent clamps, and prop combinations that can never fire.

Presets

import { LiquidGlassAndroidView, GlassPresets } from 'react-native-android-liquid-glass';

<LiquidGlassAndroidView preset="floatingTabBar" cornerRadius={32} />

preset takes navigationBar, floatingTabBar, cardOverMedia or compactControl; dispersionPreset takes subtle, glass, crystal, diamond or rainbow. Both are resolved in JS as { ...preset, ...yourProps }, so an explicit prop always wins. The raw maps are exported as GlassPresets and DISPERSION_PRESETS.

Performance

The effect captures and re-blurs the backdrop. Rough order of what to reach for:

  1. paused={true} whenever the view is mounted but not visible. This is the biggest single win: enableDynamicBackground drives a self-invalidating redraw at display refresh rate that nothing else stops, including behind a pushed navigation screen.
  2. globalDownsampleFactor to 0.5 — quarters the pixels the capture and blur touch, and the blur hides most of the resolution loss.
  3. enableDynamicBackground={false} wherever nothing actually moves.
  4. enableChromaticDispersion={false} — by far the heaviest effect.
  5. aberrationDownsample / dispersionDownsample down. (downsampleScale is clamped to 2–3 upstream, so it is not the lever it looks like.)
  6. At most one enableAdaptiveTint view per screen. It costs a periodic software View.draw() of the glass's parent subtree on the UI thread every 350 ms, on every pipeline. Drive the rest from its onGlassAppearanceChange.
  7. layerType="none" if you have large or numerous glass views — upstream forces a hardware layer, which is an off-screen texture per instance. Measure it.

Pass StyleSheet.created styles and useCallback-wrapped handlers: both components are wrapped in memo, and inline styles or arrow handlers defeat the bailout, forcing a full native prop re-commit on every ancestor render.

Do not stack many instances in a scrolling list. Keep the frame-stats demo open on a real mid-range device while tuning, and read maxTotalMs rather than totalMs — it covers every frame in the window, so spikes are visible.

Known limitations

  • Touches mostly stop at the glass. pointerEvents="none" / "box-none" now stops the glass claiming touches at the React Native level, which is what you want for a decorative panel. It cannot be complete: upstream v2.0.0's onTouchEvent returns true for every down/move/up with no opt-out, and Android dispatches to a disabled view's onTouchEvent anyway. elasticity={0} does not disable the capture. Upstream main's enablePressEffect is the real fix and will be adopted when released.
  • The native blur kernels are arm only. The AAR ships libnativegauss.so for arm64-v8a and armeabi-v7a only, and the failure surfaces as ExceptionInInitializerError / NoClassDefFoundErrorjava.lang.Errors that upstream's own catch (Exception) cannot stop. The bridge probes for the library at view creation and demotes blurMethod to boxBlur, chromaticAberrationMode to kotlin, and forces enableChromaticDispersion off, reporting NATIVE_BLUR_UNAVAILABLE through onError. So an x86_64 emulator renders a softer effect instead of crashing — but use an arm64 emulator (the default on Apple Silicon) or a device to see the real thing.
  • Props for the inactive pipeline are ignored. Nothing throws on API 30 — you just get a blur where a lens was designed. __DEV__ warns for the cases where this is most surprising (primaryShape, enableChromaticDispersion).
  • Upstream main is ahead of v2.0.0. enablePressEffect, pressScale, debugApiLevelCap and effectiveApiLevel exist on main but not in the released artifact, so they are deliberately not bridged: the module would not compile against the version people can actually install.
  • setCustomBackdropCapture is not exposed. It takes a Kotlin lambda returning a Bitmap and has no sensible JS equivalent.

Example app

17 demos covering the whole bridged surface — a home-screen showcase, a playground per prop group, the shape commands, all three events, and four copy-paste UI recipes. Screenshots live in docs/media/.

yarn                # install
yarn example start  # metro
yarn android        # build + run

Run on an arm64 emulator or a physical device.

Development

yarn typecheck      # tsc
yarn lint           # eslint
yarn codegen        # regenerate the Fabric interface/delegate to read them

Adding a prop is two steps, and the compiler enforces the second:

  1. Declare it in src/LiquidGlassAndroidViewNativeComponent.ts.
  2. Implement the generated override fun setXxx(...) in android/src/main/java/com/liquidglassandroid/LiquidGlassAndroidViewManager.kt.

See docs/BRIDGING.md for the full mechanics.

Contributing

License

MIT. The upstream Liquid-Glass-Android library is also MIT.


Made with create-react-native-library