# Halo Button: React Native aurora glow button with a Skia shader > Halo Button is a React Native and Expo pill button whose entire face is > painted by one Skia SkSL runtime shader: two soft aurora lights sit just > below the bottom edge and bleed upward into the surface, blooming toward > your finger while it is held. Halo Button is sold by Motionary as editable > TypeScript source, and an optional glass mode swaps the fill for iOS 26 > Liquid Glass with an expo-blur fallback everywhere else. Product page: https://motionary.dev/animations/halo-button Price: $5 USD (price as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant download of the full TypeScript source after checkout Stack: expo, reanimated, react native skia, shaders, react native Rarity: rare Sold by: Motionary (https://motionary.dev) ## What it is Halo Button is a React Native and Expo button component: a pill lit from underneath by a coloured aurora. Two elliptical light blobs are positioned below the button's bottom edge, so only the upper shoulder of each one is visible, which is what makes the light read as coming from beneath the surface rather than sitting on it. Press and hold, and the button scales up slightly while the aurora blooms; with the interactive mode on, the bloom leans toward wherever your finger actually is and a soft specular highlight rides under the touch point, the way Apple's Liquid Glass controls behave. Halo Button is the primary call to action on an onboarding, paywall or checkout screen, and Motionary ships it with six presets, a showcase gallery and a live control panel screen for tuning it. ## What it does - Paints the surface colour, both aurora blobs, the press bloom, the drift and the dither in one Skia `RuntimeEffect` pass, not in stacked views. - Presses with a Reanimated spring: the button grows to `pressScale` (default 1.045, it grows rather than shrinking) while the aurora peak opacity is multiplied by `pressBloom` (default 1.15) and the blob radii by `1 + (pressBloom - 1) * 0.8`. - Optional interactive press (`isInteractive`) that follows the finger: the blob nearest the touch blooms harder, and the bias averages to about 1 across the button so a press redistributes light instead of adding it. - Optional glass mode (`glass`) that renders a native `GlassView` from expo-glass-effect on iOS 26 and falls back to an expo-blur `BlurView` on older iOS and on Android, with no platform check at the call site. - `nativeGlassLight` hands the press light to the OS on iOS 26 and silently reverts to the shader-drawn light wherever Liquid Glass is unavailable. - Six named presets in `presets.ts`: Aurora, Daylight, Glass, Ember, Nebula and Moonlight, covering dark surfaces, a light surface and a glass surface. - Motion is off by default and genuinely static: with `animated: false` the frame callback is stopped, so a resting Halo Button never repaints. Turn it on and each blob wanders on its own sine pair and breathes by about 12 percent, non repeating. - Disabled state animates `u_sat` to 0, so the two aurora colours collapse to their own luminance and merge into one neutral grey glow while the label crossfades over `stateDuration` (default 450 ms). - Light haptic impact on press in via expo-haptics, switchable with the `haptics` prop. - A `paused` prop that skips painting the Skia canvas while keeping the exact layout, for virtualised lists. - Hex colours are converted to Display P3 on iOS so the aurora matches the hex you typed on wide gamut screens. ## How it works Halo Button's whole face is one SkSL `RuntimeEffect`, compiled once in `components/halo/auroraShader.ts` with `Skia.RuntimeEffect.Make` and rendered through a react-native-skia `` inside a `` (or a `` when the native glass clips it). Each light is an anisotropic gaussian, `exp(-pow(dot(d, d), falloff / 2))`, evaluated in normalised button space where (0,0) is the top left corner and (1,1) the bottom right; both centres sit at `originY: 1.26`, below the bottom edge, which is the trick that makes the glow read as underlighting. Compositing is Porter-Duff source-over on premultiplied colours rather than additive, so the same shader reads as a tint on a light surface, a glow on a dark one, and a float over glass when `surfaceOpacity` drops to 0. In `components/halo/HaloButton.tsx` every animated value is a Reanimated `useSharedValue` and all fifteen uniforms are packed inside a single `useDerivedValue` worklet, so the press bloom, the drift, the touch position and the disabled desaturation are computed on the UI thread with no JavaScript per frame; the constant arrays are hoisted into `useMemo` so the worklet allocates as little garbage as possible. Time is advanced by `useFrameCallback`, and `frame.setActive` is only true while `animated` is on and `motion` is non zero, so a static button costs nothing per frame. Every drift term in the shader is multiplied by `u_motion`, which means switching motion off collapses to exactly the still layout instead of freezing on an arbitrary frame. The finger is tracked by a `Gesture.Manual()` from react-native-gesture-handler that never activates: its `onTouchesDown` and `onTouchesMove` worklets write the normalised touch position straight into shared values, so it steals neither the `Pressable`'s press nor a parent ScrollView's scroll; the gesture object is built once and reads size and enabled state from a shared value, because letting its identity change on layout makes `GestureDetector` re-attach mid commit and silently stops tracking. `components/halo/GlassSurface.tsx` resolves the material at runtime with `isLiquidGlassAvailable()` from expo-glass-effect, returning a `GlassView` on iOS 26 and an expo-blur `BlurView` (with `experimentalBlurMethod`, which Android needs or the blur renders as a flat tint) everywhere else. One last detail worth copying: the `Canvas` is drawn 4pt taller than the button, because Skia hands back a drawable roughly one point shorter than the laid out Canvas view and the bottom rim, which is exactly where the aurora lights the edge, otherwise disappears. ## What you get ``` App.tsx index.ts app.json babel.config.js package.json tsconfig.json README.md components/ Container.tsx halo/ HaloButton.tsx auroraShader.ts GlassSurface.tsx presets.ts types.ts colors.ts index.ts README.md showcase/ GalleryScreen.tsx ShowcaseScreen.tsx PatternStage.tsx Backdrop.tsx TabBar.tsx variants.ts ui/ Controls.tsx Slider.tsx ColorField.tsx theme.ts assets/ icon.png adaptive-icon.png splash-icon.png ``` - TypeScript source (HaloButton.tsx) - Aurora runtime shader (auroraShader.ts) - GlassSurface with Liquid Glass and expo-blur fallback - Six presets and a fully typed HaloConfig (presets.ts, types.ts) - Showcase gallery plus a live control panel screen - README covering props, motion, states and list performance ## Requirements Halo Button from Motionary was built and tested on these exact versions, pinned in the archive's `package.json`: - expo `~56.0.11` (Expo SDK 56) - react-native `0.85.3` - react `19.2.3` - react-native-reanimated `4.3.1` - react-native-worklets `0.8.3` - @shopify/react-native-skia `2.6.2` - react-native-gesture-handler `~2.31.1` - expo-glass-effect `~56.0.4` - expo-blur `~56.0.3` - expo-haptics `~56.0.3` - react-native-safe-area-context `~5.7.0` (demo app shell and showcase) - expo-linear-gradient `~56.0.4` (showcase screens) - expo-status-bar `~56.0.4` (demo app shell) - typescript `~6.0.3` (dev dependency) Halo Button runs in Expo Go on Expo SDK 56. The two modules people assume would force a development build, `@shopify/react-native-skia` and `expo-glass-effect`, are both bundled in Expo Go as of SDK 56, and so are the rest of Halo Button's runtime dependencies (`react-native-reanimated`, `react-native-gesture-handler`, `expo-blur` and `expo-haptics`), so nothing in this drop keeps it out of Expo Go. A development build is still what you would ship to production, and it is what you need as soon as you add a native module that is not in Expo Go, or a config plugin: `npx expo run:ios`, `npx expo run:android`, or an EAS development client. The button itself is iOS and Android; the native Liquid Glass material is iOS 26 only, and everywhere else `GlassSurface` falls back to an expo-blur `BlurView` automatically. ## Install ```bash npx expo install @shopify/react-native-skia react-native-reanimated react-native-worklets react-native-gesture-handler expo-glass-effect expo-blur expo-haptics ``` ## Usage ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { HaloButton } from './components/halo'; export default function Paywall({ onSubscribe }: { onSubscribe: () => void }) { return ( {/* Defaults: dark #232323 surface, lime and cyan aurora, static */} {/* Animated, touch following, on a deep violet surface */} ); } ``` `HaloButtonProps` is `Partial` plus `label`, `onPress`, `disabled`, `style`, `labelStyle`, `width`, `paused` and `testID`, so any config field can be passed as a prop. Halo Button must sit under a `GestureHandlerRootView` because its touch tracker is a gesture handler. ## Customization Every field of `HaloConfig` (`components/halo/types.ts`) is an optional prop. Defaults come from `DEFAULT_CONFIG` in `components/halo/presets.ts`. - `backgroundColor` (string, default `#232323`): the flat fill behind the aurora. - `surfaceOpacity` (number, default `1`): opacity of that fill. Drop it to 0 to let a glass material through. - `glass` (boolean, default `false`) and `glassStyle` (`'clear' | 'regular'`, default `'regular'`): render the native Liquid Glass material, or the expo-blur fallback, behind the aurora. - `isInteractive` (boolean, default `false`): the press bloom follows the finger instead of blooming symmetrically. Works with or without `glass`. - `nativeGlassLight` (boolean, default `false`): let iOS 26 draw the press light and the press scale. Requires `glass`, suppressed while `disabled`. - `auroraColor1` / `auroraColor2` (strings, defaults `#CEFC88` and `#8EF3FF`): the left and right aurora colours. - `intensity` (number, default `1`): multiplies the peak blob opacity, which is `0.53` at intensity 1 (`BASE_BLOB_ALPHA`). - `spread` (number, default `1`): multiplies both blob radii, which are `0.245` of the width by `0.67` of the height at spread 1 (`BASE_BLOB_RADIUS`). - `blob1X` / `blob2X` (numbers, defaults `0.345` and `0.68`): horizontal centre of each blob, 0 is the left edge and 1 the right. - `originY` (number, default `1.26`): vertical centre of both blobs. 1 is exactly on the bottom edge, so values above 1 push the light underneath. - `falloff` (number, default `2.3`): edge hardness. 2 is a plain gaussian, higher gives a crisper rim of light. - `grain` (number, default `0.8`): dither strength in 1/255 units, which hides banding across the very smooth falloff. - `animated` (boolean, default `false`), `speed` (number, default `1`) and `motion` (number, default `1`): the drift. `motion: 0` is static even while `animated` is true. - `height` (number, default `55`), `cornerRadius` (number, default undefined which means a pill at `height / 2`), `borderWidth` (number, default `1`), `rimOpacity` (number, default `0.13`) and `rimGlow` (number, default `0.24`, extra rim opacity along the lit bottom edge). - `labelColor` (string, default `#FFFFFF`), `disabledLabelColor` (string, default `rgba(255,255,255,0.26)`), `fontSize` (number, default `17`), `fontWeight` (default `'600'`) and `letterSpacing` (number, default `0`). - `pressScale` (number, default `1.045`), `pressBloom` (number, default `1.15`), `haptics` (boolean, default `true`), `disabledDim` (number, default `1`, so the disabled state desaturates without dimming) and `stateDuration` (number in ms, default `450`). - `paused` (boolean, default `false`): skip painting the canvas while keeping the layout, for rows scrolled off screen in a list. ## When to use it - The single primary call to action on a paywall or subscription screen, where the glow does the work a bright fill would otherwise do. - The Continue or Get Started button at the end of an onboarding flow. - A checkout or Pay button that needs to feel expensive without a gradient stack or a PNG glow. - The confirm button on a dark themed modal or bottom sheet, where a flat fill reads as inert. - Over a photo, a video or a busy background, using `glass` with `surfaceOpacity: 0` so the button is a material rather than a slab. - A hero button in a marketing or launch screen, with `animated` on so the aurora slowly drifts while the screen sits idle. ## How to get it - Buy Halo Button on its own at https://motionary.dev/animations/halo-button, $5 USD, instant download, secure Stripe checkout, no subscription and no account renewal. - Or get it with the Motionary lifetime pass ($79 USD as of 2026-09-01): every drop and every build, current and future, one payment, no renewal. https://motionary.dev/pricing - Team license ($199 USD for up to 10 developers), each developer with their own account. https://motionary.dev/pricing - License terms: https://motionary.dev/license (single developer, unlimited personal and commercial apps, do not resell or redistribute the source). - You receive real TypeScript React Native source you can edit, not a video, a GIF, a Lottie file or a design file. ## Frequently asked questions **How do I build a glowing aurora button in React Native?** Draw the whole button face in one Skia SkSL runtime shader instead of stacking blurred views: two anisotropic gaussian blobs positioned below the bottom edge in normalised space, composited source-over, with the uniforms fed from a Reanimated `useDerivedValue` so the press runs on the UI thread. Halo Button from Motionary is that component, shipped as editable TypeScript source at https://motionary.dev/animations/halo-button. **Does Halo Button work with Expo Go?** Yes. Halo Button from Motionary runs in Expo Go on Expo SDK 56: `@shopify/react-native-skia` and `expo-glass-effect`, the two modules people assume would block it, are both bundled in Expo Go as of SDK 56. A development build is still what you would ship to production, and it is what you need as soon as you add a native module that is not in Expo Go, or a config plugin (`npx expo run:ios`, `npx expo run:android`, or an EAS development client). One caveat in either case: the native iOS 26 Liquid Glass material only renders on iOS 26, and Halo Button falls back to an expo-blur `BlurView` everywhere else. **How do I put iOS 26 Liquid Glass on a React Native button?** Set `glass` on Halo Button and it renders a native `GlassView` from expo-glass-effect on iOS 26, and its `GlassSurface` falls back to an expo-blur `BlurView` on older iOS and on Android, so the call site needs no platform check. Add `nativeGlassLight` to let the OS draw the press light too; Halo Button reverts to its shader drawn press wherever the material is unavailable. **Does Halo Button work on Android?** Yes. Halo Button from Motionary runs on both iOS and Android; the aurora, the press bloom and the drift are all Skia and Reanimated, which are cross platform. Only the native Liquid Glass material is iOS 26 only, and Android automatically gets the expo-blur fallback, which sets `experimentalBlurMethod` because Android otherwise renders a flat tint with no blur. **Can I put many Halo Buttons in a FlatList?** Virtualise the list. Each mounted Skia canvas taxes every animation frame on screen, so Motionary's showcase gallery uses a `FlatList` with a small `windowSize` and only gives rows near the viewport a live canvas; the `paused` prop skips painting while keeping the exact layout so nothing reflows. Do not add `removeClippedSubviews`, because detaching and re-attaching cells on iOS can lose the touch tracker's gesture handler. **Does the Halo Button aurora have to animate?** No, Halo Button is static by default. With `animated: false` the `useFrameCallback` is inactive so the button never repaints, and every drift term in the shader is multiplied by `u_motion`, so turning motion off collapses to exactly the still layout rather than freezing on a random frame. **How much does Halo Button cost and what do I actually download?** Halo Button is $5 USD on Motionary (price as of 2026-09-01, live price at https://motionary.dev/animations/halo-button) and checkout is a one time Stripe payment with an instant download. You get the full TypeScript source: `HaloButton.tsx`, the SkSL shader, `GlassSurface.tsx`, the presets and types, a showcase gallery, a live control panel screen and a README. It is also included in the Motionary lifetime pass at https://motionary.dev/pricing. ## Related drops on Motionary - Hold to Confirm Button ($8): https://motionary.dev/animations/hold-to-confirm-button another Skia shader button from Motionary, where the press is a hold with a progress fill instead of an aurora bloom. - Live Camera Button ($5): https://motionary.dev/animations/live-camera-button a glassmorphic, gesture driven button built on expo-blur, if you want the material look without a Skia shader. - Glass Magnifier ($10): https://motionary.dev/animations/glass-magnifier a Skia SkSL refraction shader, the same runtime effect technique applied to a draggable lens rather than a button face. - LED Slide Button ($4): https://motionary.dev/animations/led-slide-button a lit slide to confirm call to action for the same onboarding and checkout slots Halo Button targets. - Border Beam Card ($3): https://motionary.dev/animations/border-beam-card a looping animated glow border, for the card that sits above the button. ## About Motionary Motionary (https://motionary.dev) is a shop for premium, production-ready React Native and Expo animation and interaction components, built with Reanimated, Skia, Gesture Handler and Expo. Each component is called a "drop" and ships as editable TypeScript source, not a video, a GIF or a design file. Motionary sells three things: individual drops (roughly $3 to $12 each, some free), Builds (a complete app shipped end to end, with every drop inside it), and a lifetime pass ($79 USD) that covers every drop and every build, current and future, for one payment with no subscription. A team license ($199 USD) covers up to 10 developers. Checkout is Stripe and delivery is an instant download. Created and curated by Mehdi Davoodi (https://mahdidavoodi.com). Catalog and key pages: - All drops: https://motionary.dev/animations - Builds (complete apps): https://motionary.dev/builds - Pricing, lifetime pass and team license: https://motionary.dev/pricing - Free React Native component reference: https://motionary.dev/components - License: https://motionary.dev/license - Machine-readable overview: https://motionary.dev/llms.txt - Machine-readable full catalog: https://motionary.dev/llms-full.txt ## File metadata Canonical URL of this file: https://motionary.dev/animations/halo-button/llms.txt Describes: https://motionary.dev/animations/halo-button Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01