dist/plugins/mobile-animation-reanimated/skills/mobile-animation-reanimated/SKILL.md
React Native Reanimated 4 - shared values, animated styles, spring/timing/decay, layout animations, gesture integration, scroll-driven animations, interpolation, worklets, CSS animations
npx skillsauth add agents-inc/skills mobile-animation-reanimatedInstall 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.
Quick Guide: Reanimated 4 is New Architecture only (requires
react-native-workletsas a separate dependency). UseuseSharedValue+useAnimatedStylefor all animations. Animations run on the UI thread via worklets -- never block the JS thread. UsewithSpring(physics-based) orwithTiming(duration-based) for transitions, layout animations (entering/exiting) for mount/unmount, anduseScrollOffset(renamed fromuseScrollViewOffset) for scroll-driven animations. Reanimated 4 also introduces CSS animations/transitions as a declarative alternative to the worklet API.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST use Animated components (Animated.View, Animated.Text, etc.) for any animated styles -- passing animated styles to regular components causes errors)
(You MUST keep static styles in StyleSheet.create and only animate dynamic properties in useAnimatedStyle -- animating static values wastes UI thread resources)
(You MUST NOT mutate shared values inside useAnimatedStyle callbacks -- read only, or you cause infinite loops)
(You MUST use react-native-worklets as a separate dependency in Reanimated 4 -- the worklet Babel plugin moved from react-native-reanimated/plugin to react-native-worklets/plugin)
</critical_requirements>
Auto-detection: Reanimated, react-native-reanimated, useSharedValue, useAnimatedStyle, withSpring, withTiming, withDecay, Animated.View, Animated.Text, Animated.ScrollView, entering, exiting, FadeIn, FadeOut, SlideIn, interpolate, interpolateColor, useScrollOffset, GestureDetector, Gesture.Pan, worklet, layout animation, shared value, energyThreshold, CSS animation, react-native-worklets
When to use:
When NOT to use:
Key patterns covered:
useSharedValue) + animated styles (useAnimatedStyle)withTiming, withSpring, withDecayentering/exiting with predefined buildersGesture.Pan + shared values + withDecayuseScrollOffsetinterpolate and interpolateColor'worklet' directiveDetailed Resources:
Reanimated runs animations on the UI thread via worklets, keeping the JS thread free for business logic. The core model: shared values are reactive state that bridges JS and UI threads, animated styles derive visual properties from shared values, and animation functions (withSpring, withTiming, withDecay) drive transitions between values.
Reanimated 4 key changes from 3.x:
react-native-worklets as a separate package -- Babel plugin moved from react-native-reanimated/plugin to react-native-worklets/pluginenergyThreshold replaces restDisplacementThreshold/restSpeedThreshold in withSpringuseScrollOffset replaces useScrollViewOffset (deprecated alias remains)react-native-worklets: runOnJS -> scheduleOnRN, runOnUI -> scheduleOnUIWhen to use CSS animations vs worklets:
Backward compatibility: All v2/v3 shared value and animation APIs work unchanged in v4. CSS animations and worklet-based animations work simultaneously and interchangeably.
</philosophy>The fundamental building blocks. useSharedValue creates reactive state on the UI thread. useAnimatedStyle derives styles that update when shared values change.
import Animated, {
useSharedValue,
useAnimatedStyle,
withTiming,
} from "react-native-reanimated";
const EXPANDED_HEIGHT = 200;
const COLLAPSED_HEIGHT = 60;
const ANIMATION_DURATION = 300;
function CollapsibleCard() {
const height = useSharedValue(COLLAPSED_HEIGHT);
const animatedStyle = useAnimatedStyle(() => ({
height: height.value,
}));
const toggle = () => {
height.value = withTiming(
height.value === COLLAPSED_HEIGHT ? EXPANDED_HEIGHT : COLLAPSED_HEIGHT,
{ duration: ANIMATION_DURATION }
);
};
return (
<Pressable onPress={toggle}>
<Animated.View style={[styles.card, animatedStyle]}>
<Text>Content</Text>
</Animated.View>
</Pressable>
);
}
Why good: static styles stay in StyleSheet, only dynamic height in useAnimatedStyle, named constants for dimensions and durations, Animated.View receives the animated style
Key rules:
useAnimatedStyle -- static styles belong in StyleSheet.createuseAnimatedStyle -- it is read-onlyAnimated.* components, not regular View/TextSee examples/core.md for complete examples including React Compiler compatibility (get()/set()).
withTiming is duration-based (predictable timing). withSpring is physics-based (natural feel). Choose based on UX intent.
// Physics-based spring (natural, bouncy)
sv.value = withSpring(TARGET, { damping: 100, stiffness: 800 });
// Duration-based spring (controlled timing with spring feel)
sv.value = withSpring(TARGET, { duration: 500, dampingRatio: 0.8 });
// Timing with easing
sv.value = withTiming(TARGET, {
duration: ANIMATION_DURATION,
easing: Easing.bezierFn(0.25, 0.1, 0.25, 1),
});
Reanimated 4 spring change: restDisplacementThreshold and restSpeedThreshold are removed. Replaced by energyThreshold (relative to animation, default 6e-9). In most cases, removing the old thresholds is sufficient -- no need to set energyThreshold manually.
Duration gotcha: In v4, actual spring completion time = perceptual duration x 1.5. Divide existing duration values by 1.5 for equivalent behavior when migrating from v3.
See examples/core.md for spring config comparison and withDecay.
Predefined animations for component mount/unmount. Apply to Animated.* components via entering and exiting props.
import Animated, { FadeIn, FadeOutLeft } from "react-native-reanimated";
const ANIMATION_DURATION = 400;
const ANIMATION_DELAY = 100;
function NotificationBanner({ visible }: { visible: boolean }) {
if (!visible) return null;
return (
<Animated.View
entering={FadeIn.duration(ANIMATION_DURATION).delay(ANIMATION_DELAY)}
exiting={FadeOutLeft.duration(ANIMATION_DURATION)}
style={styles.banner}
>
<Text>New notification</Text>
</Animated.View>
);
}
Available builders: FadeIn, SlideInRight, ZoomIn, BounceIn, FlipInEasyX, LightSpeedInRight, RotateIn, PinwheelIn, and all directional variants (Up/Down/Left/Right) plus corresponding Out variants.
Modifiers: .duration(ms), .delay(ms), .springify() (with .damping(), .stiffness(), .mass()), .withInitialValues(), .withCallback(), .reduceMotion().
Performance tip: Define animation builders outside components or in useMemo -- creating them inline in render causes unnecessary object allocation.
See examples/layout-animations.md for custom builders, staggered lists, and EntryExitTransition.
Reanimated integrates with react-native-gesture-handler. Gesture callbacks are automatically workletized -- you can access shared values directly without the 'worklet' directive.
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, {
useSharedValue,
useAnimatedStyle,
withDecay,
} from "react-native-reanimated";
function DraggableCard() {
const offsetX = useSharedValue(0);
const pan = Gesture.Pan()
.onChange((e) => {
offsetX.value += e.changeX;
})
.onFinalize((e) => {
offsetX.value = withDecay({ velocity: e.velocityX, rubberBandEffect: true });
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offsetX.value }],
}));
return (
<GestureDetector gesture={pan}>
<Animated.View style={[styles.card, animatedStyle]} />
</GestureDetector>
);
}
Why good: onChange gives delta values (not absolute), onFinalize adds momentum with withDecay, gesture callbacks access shared values directly on UI thread
Key points:
onChange for incremental updates (delta), onUpdate for absolute positionGestureHandlerRootView must wrap your app near the rootSee examples/gestures.md for swipe-to-dismiss, bottom sheet, and combined gestures.
Use useScrollOffset to track scroll position as a shared value. Combine with interpolate for parallax, collapsing headers, and fade effects.
import Animated, {
useAnimatedRef,
useScrollOffset,
useAnimatedStyle,
interpolate,
Extrapolation,
} from "react-native-reanimated";
const HEADER_MAX = 200;
const HEADER_MIN = 60;
function CollapsibleHeader() {
const scrollRef = useAnimatedRef<Animated.ScrollView>();
const scrollOffset = useScrollOffset(scrollRef);
const headerStyle = useAnimatedStyle(() => ({
height: interpolate(
scrollOffset.value,
[0, HEADER_MAX - HEADER_MIN],
[HEADER_MAX, HEADER_MIN],
Extrapolation.CLAMP
),
}));
return (
<>
<Animated.View style={[styles.header, headerStyle]} />
<Animated.ScrollView ref={scrollRef}>
{/* content */}
</Animated.ScrollView>
</>
);
}
Why good: useScrollOffset auto-detects horizontal/vertical, no manual scroll event handler needed, Extrapolation.CLAMP prevents values outside the range
Reanimated 4 rename: useScrollViewOffset -> useScrollOffset (deprecated alias remains temporarily).
See examples/scroll-animations.md for parallax, sticky elements, and scroll-to-hide tab bar.
Map one value range to another. interpolate for numbers, interpolateColor for color transitions.
import { interpolate, interpolateColor, Extrapolation } from "react-native-reanimated";
// Number interpolation: scroll position -> opacity
const opacity = interpolate(
scrollY.value,
[0, 100], // input range
[1, 0], // output range
Extrapolation.CLAMP
);
// Color interpolation: progress -> background
const backgroundColor = interpolateColor(
progress.value,
[0, 0.5, 1], // input range
["#FF0000", "#FFFF00", "#00FF00"] // output colors (red -> yellow -> green)
);
Extrapolation options: CLAMP (cap at edges), EXTEND (extrapolate linearly), IDENTITY (return input value). Can set left/right independently: { extrapolateLeft: Extrapolation.CLAMP, extrapolateRight: Extrapolation.EXTEND }.
interpolateColor modes: 'RGB' (default) or 'HSV'. HSV produces more perceptually uniform transitions for hue changes.
See examples/core.md for multi-step interpolation and color transition examples.
Functions that run on the UI thread. Mark with 'worklet' directive. Reanimated auto-workletizes callbacks in useAnimatedStyle, gesture handlers, and animation callbacks -- you only need explicit 'worklet' for standalone helper functions.
import { scheduleOnRN } from "react-native-worklets";
function clampValue(value: number, min: number, max: number) {
"worklet";
return Math.min(Math.max(value, min), max);
}
// Use in useAnimatedStyle -- auto-workletized, no directive needed
const style = useAnimatedStyle(() => ({
opacity: clampValue(progress.value, 0, 1),
}));
Reanimated 4 threading changes:
| Reanimated 3 | Reanimated 4 (react-native-worklets) |
|--------------------------|--------------------------------------|
| runOnJS(fn)("arg") | scheduleOnRN(fn, "arg") |
| runOnUI(fn)("arg") | scheduleOnUI(fn, "arg") |
When you need explicit 'worklet':
scheduleOnUIWhen you do NOT need it:
useAnimatedStyle callbacks (auto-workletized)Declarative animation API modeled after web CSS. Best for state-driven animations. Worklet API remains for gesture/scroll-driven scenarios.
import Animated from "react-native-reanimated";
const TRANSITION_DURATION = 300;
function ToggleBox({ expanded }: { expanded: boolean }) {
return (
<Animated.View
style={{
height: expanded ? 200 : 60,
opacity: expanded ? 1 : 0.5,
transitionProperty: "height, opacity",
transitionDuration: `${TRANSITION_DURATION}ms`,
transitionTimingFunction: "ease-in-out",
}}
/>
);
}
CSS animation keyframes:
const PULSE_DURATION = 1000;
<Animated.View
style={{
animationName: {
from: { transform: [{ scale: 1 }] },
to: { transform: [{ scale: 1.1 }] },
},
animationDuration: `${PULSE_DURATION}ms`,
animationIterationCount: "infinite",
animationDirection: "alternate",
animationTimingFunction: "ease-in-out",
}}
/>
When to use CSS vs worklets:
<red_flags>
High Priority Issues:
View/Text instead of Animated.View/Animated.Text -- causes silent failure or crashuseAnimatedStyle -- causes infinite re-evaluation loopsreact-native-reanimated/plugin in Babel config with Reanimated 4 -- must use react-native-worklets/plugin (and it must be last in the plugins array)useAnimatedStyle instead of keeping them in StyleSheet -- wastes UI thread resourcesrestDisplacementThreshold/restSpeedThreshold in withSpring -- removed in v4, replaced by energyThresholdMedium Priority Issues:
useMemorunOnJS/runOnUI instead of scheduleOnRN/scheduleOnUI -- old API moved to react-native-workletsGestureHandlerRootView at app root -- gestures silently fail without ituseAnimatedGestureHandler -- removed in v4, migrate to Gesture Handler 2's Gesture APIGotchas and Edge Cases:
withSpring duration: actual completion time = perceptual duration x 1.5 -- divide v3 duration values by 1.5 when migratinguseScrollOffset renamed from useScrollViewOffset -- deprecated alias still works temporarily.value access is synchronous on UI thread but asynchronous on JS thread -- don't rely on immediate reads after writes on JS threaduseWorkletCallback removed -- replace with useCallback + 'worklet' directivesv.get() and sv.set() instead of direct .value access when using React CompilercombineTransition removed -- use EntryExitTransition.entering(entering).exiting(exiting)undefined to resetnativeID internally -- don't overwrite it on animated components</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST use Animated components (Animated.View, Animated.Text, etc.) for any animated styles -- passing animated styles to regular components causes errors)
(You MUST keep static styles in StyleSheet.create and only animate dynamic properties in useAnimatedStyle -- animating static values wastes UI thread resources)
(You MUST NOT mutate shared values inside useAnimatedStyle callbacks -- read only, or you cause infinite loops)
(You MUST use react-native-worklets as a separate dependency in Reanimated 4 -- the worklet Babel plugin moved from react-native-reanimated/plugin to react-native-worklets/plugin)
Failure to follow these rules will cause animation failures, infinite loops, and crashes on the UI thread.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events