About this animation
A welcome hero with a touch-responsive gradient mesh and configurable palette, copy, fonts, and title size.
Why this one
The background is a live SkSL shader rather than a looping image or video. Five independently orbiting colors respond to your finger; a sixth white light swells inside the mesh and fades on release. Three curated palettes and a shader-level contrast guard keep the copy readable without flattening the entire scene.
How it works
onLayoutmeasures the container. One Canvas renders the mesh and optional Lucide kit tile.- A UI-thread clock moves five points on Lissajous orbits with 11–19-second periods. Softened inverse-distance weights blend their colors.
- Pan and tap compete through Gesture Handler. The nearest point is selected at touch-down; independent springs follow the finger and pull that point away from its orbit. Release restores the orbit and fades the white light over 600 ms.
- A fixed 4% grain breaks up banding. A transparent-to-indigo veil starts at 45% height. From 51% height, the shader bounds every sRGB channel to 0.36 after grain and touch light, guaranteeing readable settled copy even in the brightest state.
- One entrance progress reveals the mesh from black with 1.15→1 zoom over 1,200 ms. Words start at 300 ms, stagger by 60 ms, and fade, sharpen from 8 px blur, and rise 12 pt. Buttons finish the entrance with a real blur material and independent press springs.
- Autoplay belongs to the demo. The public ref exposes
replay(),touch(x, y)with normalized coordinates, andrelease().
Install
npx expo install @shopify/react-native-skia expo-blur react-native-gesture-handler react-native-reanimated react-native-workletsWrap your app in GestureHandlerRootView. Load your fonts before mounting. On web, initialize CanvasKit using your app's Skia loading boundary before importing this component; the host catalog uses its withSkia boundary. No images, network requests, or additional native modules are needed.
Usage
import GradientMeshHero from './gradient-mesh-hero';
export function WelcomeScreen() {
return <GradientMeshHero onGetStarted={() => openSignup()} onSignIn={() => openSignin()} />;
}Customize the palette and content:
<GradientMeshHero
palette="lagoon"
brand="Your studio"
title={'A fresh start.\nEvery day.'}
subtitle="Find a little space for your ideas."
primaryLabel="Get started"
secondaryLabel="Sign in"
fontFamily="YourSansFont"
titleFontFamily="YourDisplayFont"
titleSize={60}
speed={0.8}
grain={0.03}
entranceDuration={1200}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| palette | 'dusk' | 'lagoon' | 'ember' | dusk | Five-color palette. |
| brand | string | Welcome | Wordmark. |
| brandAccessory | ReactNode | — | Optional wordmark accessory. |
| showBrandTile | boolean | false | Aurora kit tile in the same Canvas. |
| title | string | Create something / beautiful. | Newlines set line breaks; longer lines wrap. |
| subtitle | string | A little inspiration goes a long way. | Supporting copy. |
| primaryLabel | string | Continue | Main action text. |
| secondaryLabel | string | Sign in | Glass action text. |
| onGetStarted / onSignIn | function | — | Action callbacks on JS. |
| fontFamily / titleFontFamily | string | System | Body/control and headline/wordmark fonts. |
| titleSize | number | 61.36 | Headline size at 390 pt; scales down on smaller containers. |
| entranceDuration | number | 1200 | Entrance milliseconds. |
| speed | number | 1 | Nonnegative orbit speed multiplier. |
| grain | number | 0.04 | Grain amplitude, clamped to 0–0.04. |
| style | StyleProp<ViewStyle> | — | Container style. |
| testID | string | — | Root test identifier. |
The demo's PALETTE constant selects its art direction. Brand starts at 8% height, copy at 52%, and controls at min(84% height, height − 146 pt). This preserves two 56 pt targets, a 10 pt gap, and 24 pt bottom breathing room at 320×694 and 390×844. Keep custom copy short; large accessibility text may require a scrollable host.
Compatibility
Targets Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, and Skia 2.14.0. Native dependencies are bundled in the matching Expo Go. Android uses BlurTargetView and dimezisBlurViewSdk31Plus, with a translucent fallback on Android versions below API 31. See the SDK 58 BlurView documentation.
Accessibility
Both actions have button roles and labels with 56 pt hit areas. The mesh has an explicit screen-reader activation action, and a tap provides the same color attraction as a drag. Text is separately exposed as a single headline. Buttons sit outside the mesh GestureDetector. Reduced motion fixes the mesh at time zero, disables touch distortion and light, removes zoom, translation, blur, and press scaling, and uses a 220 ms entrance fade.
For settled copy, the worst-case channel ceiling of 0.36 yields white headline contrast of approximately 6.71:1 and 80%-white subtitle contrast of approximately 5.00:1, including white interaction light and grain. Entrance opacity intentionally begins at zero. The wordmark region has the same channel guard plus a diffuse shadow. Icons: Lucide (ISC); the sparkles path is copied from the repository kit.
Layout
Font metrics measured from the bundled Instrument Serif font keep both demo headline lines within 88% width: 338.6/319.1 pt at 390 pt wide and 276.9/262.9 pt at 320 pt wide. The wordmark stays on the left; no control occupies the viewer close-button area. Copy and controls have separate vertical regions; the shorter screen moves controls up to keep them visible. Autoplay attracts a color from 1.05 seconds and drags it through the center before 2.3 seconds. Grain, light, veil, zoom, and contrast are computed inside the shader, with no solid effect block over the text.




