From rn-lib-claude
Use when implementing animations or gestures in a React Native library — GPU-only properties, Reanimated v3/v4 worklets, useDerivedValue, GestureDetector press states, React Compiler compatibility.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rn-lib-claude:animationsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reanimated v3+ + Gesture Handler v2. New Architecture only. No RN core `Animated` API.
Reanimated v3+ + Gesture Handler v2. New Architecture only. No RN core Animated API.
Animating layout properties causes the layout engine to recalculate on every frame → dropped frames.
// ❌ Causes layout recalculation — never animate these
width, height, top, left, right, bottom
margin, padding, borderWidth, flex
// ✅ GPU-accelerated — animate freely
transform: [{ translateX }, { translateY }, { scale }, { rotate }]
opacity
// ❌ Animating width
const width = useSharedValue(100)
const style = useAnimatedStyle(() => ({ width: width.value }))
// ✅ Use scale transform instead
const scale = useSharedValue(1)
const style = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }] }))
import Animated, {
useSharedValue,
useAnimatedStyle,
useDerivedValue,
useAnimatedReaction,
withSpring,
withTiming,
withSequence,
withDelay,
withRepeat,
runOnJS,
interpolate,
interpolateColor,
Extrapolation,
cancelAnimation,
} from 'react-native-reanimated'
// Always Animated.View from reanimated — never from react-native
useDerivedValue vs useAnimatedReaction// ✅ useDerivedValue — compute a new shared value from another
// Runs on UI thread. Use for transforming values.
const rotation = useDerivedValue(() => `${progress.value * 360}deg`)
const clampedScale = useDerivedValue(() =>
interpolate(rawScale.value, [0, 1], [0.8, 1.2], Extrapolation.CLAMP)
)
// ✅ useAnimatedReaction — respond to a shared value with a side effect
// Use ONLY for side effects (calling JS, triggering haptics, updating refs).
// Never use it to compute another value — that's useDerivedValue's job.
useAnimatedReaction(
() => progress.value,
(current, previous) => {
if (current >= 1 && previous !== null && previous < 1) {
runOnJS(onComplete)() // ← side effect: call JS
}
},
)
When a component needs animated press feedback, use GestureDetector + shared values. Pressable callbacks fire on the JS thread and can't drive smooth animations.
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated'
type AnimatedButtonProps = {
onPress?: () => void
children: ReactNode
}
function AnimatedButton({ onPress, children }: AnimatedButtonProps) {
const scale = useSharedValue(1)
const opacity = useSharedValue(1)
const tap = Gesture.Tap()
.onBegin(() => {
scale.value = withSpring(0.95, { damping: 15, stiffness: 300 })
opacity.value = withTiming(0.8, { duration: 80 })
})
.onFinalize((_, success) => {
scale.value = withSpring(1, { damping: 15, stiffness: 300 })
opacity.value = withTiming(1, { duration: 120 })
if (success && onPress) runOnJS(onPress)()
})
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}))
return (
<GestureDetector gesture={tap}>
<Animated.View style={animatedStyle}>{children}</Animated.View>
</GestureDetector>
)
}
function Draggable({ children }: { children: ReactNode }) {
const x = useSharedValue(0)
const y = useSharedValue(0)
const startX = useSharedValue(0)
const startY = useSharedValue(0)
const pan = Gesture.Pan()
.onStart(() => {
startX.value = x.value
startY.value = y.value
})
.onUpdate(e => {
x.value = startX.value + e.translationX
y.value = startY.value + e.translationY
})
.onEnd(() => {
x.value = withSpring(0)
y.value = withSpring(0)
})
const style = useAnimatedStyle(() => ({
transform: [{ translateX: x.value }, { translateY: y.value }],
}))
return (
<GestureDetector gesture={pan}>
<Animated.View style={style}>{children}</Animated.View>
</GestureDetector>
)
}
const composed = Gesture.Simultaneous(panGesture, pinchGesture) // both fire
const composed = Gesture.Exclusive(tapGesture, longPress) // first wins
const composed = Gesture.Race(tapGesture, panGesture) // first cancels rest
const style = useAnimatedStyle(() => {
const rotate = interpolate(progress.value, [0, 1], [0, 360], Extrapolation.CLAMP)
const color = interpolateColor(progress.value, [0, 1], ['#ff0000', '#00ff00'])
return {
transform: [{ rotate: `${rotate}deg` }],
backgroundColor: color,
}
})
Never hardcode spring/timing values. Expose as props so consumers can tune:
import type { WithSpringConfig, WithTimingConfig } from 'react-native-reanimated'
type SliderProps = {
springConfig?: WithSpringConfig
timingConfig?: WithTimingConfig
}
const DEFAULT_SPRING: WithSpringConfig = { damping: 15, stiffness: 150, mass: 1 }
function Slider({ springConfig = DEFAULT_SPRING }: SliderProps) {
// use springConfig in withSpring calls
}
Reanimated 4 introduces breaking API changes. If targeting both v3 and v4:
// v3
import { runOnUI } from 'react-native-reanimated'
runOnUI(() => { 'worklet'; scale.value = 1 })()
// v4
import { scheduleOnUI } from 'react-native-reanimated'
scheduleOnUI(() => { 'worklet'; scale.value = 1 })()
Reanimated 4 requires New Architecture — already enforced by this plugin.
React Compiler optimizes shared value access. Use .get()/.set() methods:
// ❌ Direct .value — React Compiler may misoptimize
const scale = useSharedValue(1)
scale.value = withSpring(1)
const current = scale.value
// ✅ .get()/.set() — React Compiler safe (Reanimated 4+)
scale.set(withSpring(1))
const current = scale.get()
console.log inside worklets — crashes New Architecture UI thread'worklet' directive or be native// ✅ Worklet-safe utility
function clamp(value: number, min: number, max: number): number {
'worklet'
return Math.min(Math.max(value, min), max)
}
// UI thread → JS thread
runOnJS(jsCallback)(arg1, arg2)
// JS thread → UI thread (v3)
runOnUI(() => { 'worklet'; sharedValue.value = 1 })()
// JS thread → UI thread (v4)
scheduleOnUI(() => { 'worklet'; sharedValue.value = 1 })()
Add to package.json when using animations/gestures:
"peerDependencies": {
"react-native-reanimated": ">=3.0.0",
"react-native-gesture-handler": ">=2.0.0"
},
"peerDependenciesMeta": {
"react-native-reanimated": { "optional": true },
"react-native-gesture-handler": { "optional": true }
}
npx claudepluginhub shankulkarni/claude-plugin-marketplace --plugin rn-lib-claudeCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.