Animations
Skill Shankulkarni/RN-Lib-Claude/plugins/rn-lib-claude/skills/animations
Ship production-grade RN libraries faster — scaffold, audit, and publish with Claude Code.
npx -y skills add Shankulkarni/RN-Lib-Claude --skill animationsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
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.
SKILL.md
7.3 KB, as published. Nobody here has run it
Animations + Gestures
Reanimated v3+ + Gesture Handler v2. New Architecture only. No RN core Animated API.
CRITICAL — Only Animate GPU-Accelerated Properties
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 }] }))
Reanimated Imports
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
}
},
)
Animated Press State (GestureDetector — not Pressable callbacks)
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>
)
}
Pan Gesture (Draggable)
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>
)
}
Composing Gestures
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
Interpolation
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,
}
})
Animation Configs — Accept as Props
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 — API Changes (New Architecture Required)
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 Compatibility
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()
Worklet Rules (critical)
- No
console.loginside worklets — crashes New Architecture UI thread - No closures over JS objects — only capture primitives or other shared values
- No async/await — worklets are synchronous
- No React hooks — worklets run on UI thread, not React fiber
- Functions called from worklets must have
'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)
}
JS ↔ UI Thread
// 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 })()
Peer Dep Reminder
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 }
}