react-native-status-edge
v0.3.3
Published
[Development preview] A high-performance, Skia-powered fluid status indicator and dynamic notch animation library for React Native.
Maintainers
Readme
react-native-status-edge
🚧 Development preview — not a production release. This is a work-in-progress (
v0.3.3). APIs, behavior, and native geometry may change without notice, and some platforms/devices are still being calibrated. Use at your own risk. See License — this is proprietary, source-available software, not open source.
A high-performance, Skia-powered fluid status indicator and dynamic notch animation library for React Native.
It detects your device's screen cutout (Notch, WaterDrop, Dot/punch-hole, Dynamic Island, or None) and renders a glowing comet that follows the cutout boundary — perfect as a "loading" indicator that wraps the camera.
Requirements
- React Native >= 0.71 (New Architecture / TurboModules)
- Android API 31+ (Android 12+) for runtime cutout detection
- iOS 13+ / iPhone 11+ for notch/island detection
- Edge-to-edge rendering must be enabled so the overlay can draw over the status bar / cutout (the setup wizard configures this for you — see below)
Installation
npm install react-native-status-edgePeer dependencies
The library renders with Skia and animates with Reanimated. Install:
npm install @shopify/react-native-skia react-native-reanimatedIf you use Reanimated 4, also install its worklets runtime:
npm install react-native-workletsReanimated requires its Babel plugin (
react-native-worklets/pluginfor v4, orreact-native-reanimated/pluginfor v3) to be the last entry in yourbabel.config.js.
Setup (runs automatically)
When you install the package, a setup step runs automatically (via
postinstall): it checks your peer dependencies and enables Android
edge-to-edge (the transparent top area the overlay needs). You don't need to
run any command.
To re-run it manually (e.g. after adding native folders or on Expo prebuild):
npx react-native-status-edge setup # interactive setup
npx react-native-status-edge doctor # just check peer dependenciesiOS
cd ios && pod installWhy edge-to-edge?
StatusEdge draws the glow over the status-bar / cutout area. If your app is
not edge-to-edge (i.e. the top of the screen is opaque), the overlay is
clipped and the animation breaks — most visibly on WaterDrop and other
top-attached cutouts. The wizard sets edgeToEdgeEnabled=true in
android/gradle.properties (React Native 0.81+). iOS already extends views
under the notch/island, so no change is needed there.
Usage
Basic — show a loading animation
import { StatusEdge } from 'react-native-status-edge';
export default function App() {
return (
<>
{/* Your app content */}
<StatusEdge isLoading color="#00FF00" strokeWidth={3} />
</>
);
}Control the loading state
import { useState } from 'react';
import { Button, View } from 'react-native';
import { StatusEdge } from 'react-native-status-edge';
export default function App() {
const [isLoading, setIsLoading] = useState(false);
const handleFetch = async () => {
setIsLoading(true);
await fetchData();
setIsLoading(false);
};
return (
<View style={{ flex: 1 }}>
<Button title="Fetch" onPress={handleFetch} />
<StatusEdge isLoading={isLoading} color="#6366F1" strokeWidth={4} />
</View>
);
}useStatusEdge — access raw cutout data
import { useStatusEdge } from 'react-native-status-edge';
export default function DebugScreen() {
const data = useStatusEdge();
if (!data) return null;
console.log(data.cutoutType); // 'Notch' | 'WaterDrop' | 'Dot' | 'Island' | 'None'
console.log(data.cutoutRects); // [{ x, y, width, height }] in dp
console.log(data.cameraCircles); // [{ cx, cy, r }] in dp (Android, Dot)
return null;
}API
<StatusEdge /> props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| isLoading | boolean | false | Show/hide the animation |
| color | string | '#00FF00' | Color of the glow/comet (any CSS color) |
| strokeWidth | number | 3 | Comet/glow stroke thickness in dp |
| animation | AnimationStyle | 'trace' | Animation style (see below) |
| durationMs | number | 2000 / 2600 | One full cycle in ms (travel / breathing-pulse) |
Animation styles
Every style works on every cutout type (Notch, WaterDrop, Dot, Island, None).
| animation | Behaviour |
|-------------|-----------|
| trace | The original per-cutout comet: a beam enters top-left, traces the cutout, exits top-right. Default — existing usage is unchanged. |
| clockwise | The beam travels the top edge + cutout continuously, clockwise (Dot/Island orbit the shape). |
| counterclockwise | Same path, reversed direction. |
| breathing | No travel — the full screen border + cutout outline glow smoothly fade in and out. |
| pulse | No travel — the full screen border + cutout outline emit a quick double "heartbeat" flash, then rest. |
useStatusEdge()
Returns StatusEdgeData | null. It is null while the native module is loading
and if detection fails (see Known limitations).
interface StatusEdgeData {
cutoutType: 'Notch' | 'WaterDrop' | 'Dot' | 'Island' | 'None';
/** Bounding rectangles of all cutouts, in dp. */
cutoutRects: Array<{ x: number; y: number; width: number; height: number }>;
/** Exact camera circle(s), in dp. Android only; populated for Dot/Island. */
cameraCircles: Array<{ cx: number; cy: number; r: number }>;
/** Top safe-area inset, in dp. */
safeAreaTop: number;
/** Index into `cutoutRects` of the primary cutout (Notch/WaterDrop render around it). */
mainRectIndex?: number;
/** SVG-like polyline of the cutout path, in physical px. Android only. */
cutoutPathSvg?: string;
/** Bounding box of the cutout path, in dp. Android only. */
cutoutPathBounds?: { x: number; y: number; width: number; height: number } | null;
}Exported types: StatusEdgeData, CutoutType, CutoutRect, CameraCircle,
StatusEdgeProps, AnimationStyle. src/types.ts is the source of truth.
Cutout types
| Type | Description |
|------|-------------|
| Notch | Wide notch attached to the top edge |
| WaterDrop | Narrow teardrop notch attached to the top edge |
| Dot | Small punch-hole camera |
| Island | Dynamic Island / wide floating pill |
| None | No cutout detected |
How it works
- Native detection — On Android,
DisplayCutout(API 31+) provides the bounding rects, safe inset, and cutout path. For a center punch-hole on Samsung One UI the OS reports a tall safe-area stripe (not the bare circle), so the camera circle is taken from the center of that box. On iOS the device model identifier is mapped to a cutout type with approximate dimensions. On iOS, every model from the iPhone 11 onward (through the iPhone 17 family, 16e and Air) is mapped to its cutout type and dimensions, with a safe-area fallback for future devices. - Path construction — A Skia path traces the cutout boundary; an EvenOdd clip keeps the glow outside the cutout interior.
- Animation — Travelling styles (
trace/clockwise/counterclockwise) drive astart/endoffset along the path;breathing/pulseanimate the opacity of the full screen-border + cutout outline. All are layered with threeBlurMaskpasses.
Known limitations
This is a development preview. Current known gaps:
- Orientation: cutout geometry is read once on mount; rotating the device may misalign the overlay until remount. Designed for portrait.
- iOS dimensions are hardcoded approximations per device family; the
simulator always reports
None.cameraCirclesis Android-only. - Samsung Dot vertical centering is calibrated to the geometric center of the reported cutout stripe; a few-dp per-device offset may remain.
- Detection failures currently surface as
null(no error channel yet).
License
Proprietary — © 2026 Armağan Tambova. All rights reserved.
You may use this library, unmodified, as a dependency inside your own application (including commercial apps). You may not redistribute it, create derivative works or new versions, use it in tutorials/educational material, or monetize the library itself. All development and distribution rights are reserved by the author. See LICENSE for the full terms.
This is not open source and external contributions, forks, and derivatives are not accepted. For any use beyond the granted permissions, contact [email protected].
