skills/expo-motion/SKILL.md
Expo/React Native (RN) iOS/Android motion. Use for animation, gestures, transitions, scroll effects, Skia canvas/shader work, or unspecified Expo/RN animation. Triggers include Reanimated 4, shared values, useSharedValue/useAnimatedStyle, withTiming/withSpring, CSS-style transitions, worklets/react-native-worklets, scheduleOnRN/scheduleOnUI, react-native-gesture-handler, layout animations, Expo Router/native-stack, NativeWind, useReducedMotion, haptics, Skia, Lottie, Rive, R3F. Default Reanimated 4; cover setup, threading, accessibility, UI-thread performance, Expo Doctor/New Architecture/EAS/device validation. Requires New Architecture.
npx skillsauth add bjornmelin/dev-skills expo-motionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Production motion for Expo and React Native apps on iOS and Android. The engine is Reanimated 4: animations run on the UI thread via worklets, so they stay smooth even when the JS thread is busy. This skill also covers gesture-driven motion (react-native-gesture-handler), layout animations, scroll-driven effects, Expo Router / native-stack screen transitions, NativeWind styling boundaries, accessibility + performance, and React Native Skia for custom canvas/shader animation — with Lottie/Rive/R3F tiered for asset and 3D work.
Current-state truth (bake into every answer): Reanimated 4.x requires the New Architecture (Fabric) — RN 0.76+ / Expo SDK 52+ (Reanimated 3 / old-arch advice differs and is unmaintained). Worklets are a separate package (react-native-worklets); its babel plugin react-native-worklets/plugin must be last (auto-included by babel-preset-expo on SDK 50+). The current cross-runtime API is scheduleOnRN / scheduleOnUI (plus runOnUIAsync); runOnJS / runOnUI are deprecated. Expo SDK 56 bundles Reanimated 4.3.1. Keep the body lean — read the matching references/*.md before non-trivial work in a domain.
Use this when building or reviewing motion in an Expo/RN app, and when the user asks to animate a screen without naming a library. Recommend Reanimated by default for:
references/decision-matrix.md).Risk level: LOW — animation libraries with a minimal security surface. If the user already chose a tool, respect it.
Not this skill — route instead: Web 3D / Three.js / React Three Fiber (incl. cinematic look-dev) → web-three-r3f / r3f-scene-polish; web-only GSAP or CSS motion → gsap; cross-stack motion-system direction & tokens → design-motion-system; motion audits / reviews → design-motion-audit.
npx expo install react-native-reanimated react-native-worklets react-native-gesture-handler
# Skia (optional, native module): npx expo install @shopify/react-native-skia
npx expo install --check # keep versions Expo-compatible (don't trust npm-latest)
app.json/app.config newArchEnabled: true; default on recent SDKs). Reanimated 4 will not work on the old architecture.babel.config.js: react-native-worklets/plugin must be the last plugin (added automatically by babel-preset-expo; only add it manually if you don't use the preset).GestureHandlerRootView (or use Expo Router's root layout).references/validation.md).import Animated, { useSharedValue, useAnimatedStyle, withSpring } from "react-native-reanimated";
const x = useSharedValue(0); // UI-thread state
const style = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }] }));
// drive it: x.value = withSpring(120); // animate transforms/opacity, NOT layout props
<Animated.View style={style} />;
.value only inside worklets — never during render or on the JS thread.import { Gesture, GestureDetector } from "react-native-gesture-handler";
const pan = Gesture.Pan().onUpdate((e) => { x.value = e.translationX; })
.onEnd(() => { x.value = withSpring(0); });
<GestureDetector gesture={pan}><Animated.View style={style} /></GestureDetector>;
import Animated, { FadeIn, FadeOut, LinearTransition, ReduceMotion } from "react-native-reanimated";
<Animated.View entering={FadeIn.duration(250).reduceMotion(ReduceMotion.System)}
exiting={FadeOut} layout={LinearTransition} />;
scheduleOnRN(fn, ...args) (current; args passed directly). runOnJS/runOnUI are deprecated.useReducedMotion(); pair feedback with expo-haptics.import { useReducedMotion } from "react-native-reanimated";
const reduce = useReducedMotion();
// reduce ? x.value = 120 : x.value = withSpring(120);
import { Canvas, Circle } from "@shopify/react-native-skia";
const r = useSharedValue(20); // animate r.value with withTiming(...)
<Canvas style={{ flex: 1 }}><Circle cx={100} cy={100} r={r} color="cyan" /></Canvas>;
references/recipes.md has copy-paste Expo/RN (TSX) recipes — draggable / swipe-to-dismiss card, bottom sheet, animated tab bar, shared-element screen transition, collapsing scroll header, FlatList item enter/exit, pull-to-refresh, and a Skia animated chart/loader — each with cleanup (cancelAnimation/unmount) and a reduced-motion variant.
transform/opacity, not layout props (width/height/top/left) — layout props force reflow off the compositor.setState per frame. Read .value only in worklets.'worklet' where not auto-workletized; cross runtimes with scheduleOnRN/scheduleOnUI, not the deprecated runOnJS/runOnUI.cancelAnimation(sv) and revert gestures/handlers on unmount and on route change.useReducedMotion() / .reduceMotion(ReduceMotion.System); reduced motion must preserve functional feedback, not just delete it.expo install --check); verify the New Architecture is on; prove native motion on a development build/device.sharedValue.value during render or on the JS thread.runOnJS in a high-frequency (per-frame/gesture) callback, or leave the worklets babel plugin out / not last.| Read | When |
|---|---|
| references/reanimated-core.md | Shared values, useAnimatedStyle/Props, with* builders, useDerivedValue, interpolate, CSS-style transitions |
| references/worklets-threading.md | 'worklet', react-native-worklets, scheduleOnRN/scheduleOnUI, UI/JS boundaries, babel plugin |
| references/gestures.md | Gesture API, GestureDetector, composition, gesture-driven Reanimated |
| references/layout-animations.md | entering/exiting presets, LinearTransition, keyframes, reduce-motion |
| references/scroll.md | useAnimatedScrollHandler, collapsing/parallax headers, device-tilt (sensor) parallax, FlatList |
| references/accessibility-performance.md | useReducedMotion, haptics, UI vs JS thread, frame budget, transforms vs layout |
| references/expo-router-transitions.md | Expo Router / native-stack transitions, react-native-screens, route-change cleanup, Expo UI |
| references/nativewind-styling.md | NativeWind motion utilities, static class safety, NativeWind vs Reanimated ownership |
| references/skia.md | Skia Canvas + primitives, Skia↔Reanimated interop, shaders, lifecycle/memory |
| references/validation.md | Expo Doctor, expo install --check, New Architecture, EAS/dev build, Jest+Reanimated, device proof |
| references/assets-lottie-rive-3d.md | Lottie / Rive / R3F asset & 3D motion (tiered) |
| references/recipes.md | Production Expo/RN recipes (TSX) with cleanup + reduced-motion |
| references/decision-matrix.md | Reanimated vs CSS-transitions vs Layout Animations vs Skia vs Lottie/Rive vs NativeWind vs native-stack |
expo-motion-audit CLIThis repo ships a Rust CLI, expo-motion-audit, that statically audits Expo/RN motion code (JS/TS/JSX/TSX) and config — missing 'worklet', shared-value misuse on the JS thread, deprecated runOnJS/runOnUI, layout-prop animation, missing reduced-motion, missing cancelAnimation, and config checks (react-native-worklets/plugin presence + last-ordering, New-Architecture flag, Expo package compatibility). Optional — if not installed, proceed with the guidance above.
# Install once (from this repo): cargo install --path crates/expo-motion-audit --locked --force
expo-motion-audit scan --root . --format json
expo-motion-audit scan --root . --categories worklets-threading,config
Treat findings as leads — verify each against the current code before changing behavior. Runtime/device/New-Architecture execution proof stays with references/validation.md / Expo Doctor.
development
Pre-PR multi-model review, parallel opus and codex exec adversarial lanes, then adversarial verification of merged findings. Read-only. Use before shipping nontrivial diffs.
tools
Independent gpt-5.6 diff review via the Codex CLI, normal or steerable adversarial with JSON findings. Use before shipping nontrivial changes.
development
Delegate implementation, investigation, or bulk work to gpt-5.6 codex via pinned codex exec. Use for clear-spec builds, migrations, debugging, or any task MODELS.md routes to codex.
development
Adversarial pre-mortem: imagine the plan failed, work backwards to surface risky assumptions + irreversible bets, then harden them. Proactively offer it (after the current request; confirm first) before a hard-to-reverse or one-way-door call (API, schema, framework, a hire), an all-upside plan, or unvalidated assumptions. Also on request.