AniUI

Animate

Animation presets, spring configs, and reusable hooks for Reanimated 4.

Installation#

npx @aniui/cli add animate

Spring Presets

bouncy

Playful bounces damping: 12, stiffness: 150

Entering Animations

fadeInUp
Web preview — components render natively on iOS & Android
import { entering, exiting, springs } from "@/components/ui/animate";
import Animated, { withSpring } from "react-native-reanimated";

// Layout animations
<Animated.View entering={entering.fadeInUp} exiting={exiting.fadeOut}>
  <Card>...</Card>
</Animated.View>

// Spring physics
translateX.value = withSpring(0, springs.snappy);

Usage#

app/index.tsx
import { entering, exiting, springs } from "@/components/ui/animate";
import Animated, { withSpring } from "react-native-reanimated";

// Layout animations
<Animated.View entering={entering.fadeInUp} exiting={exiting.fadeOut}>
  <Card>...</Card>
</Animated.View>

// Spring physics
translateX.value = withSpring(0, springs.snappy);

Spring Presets#

Pre-tuned spring physics configs for withSpring. Each preset provides a different feel -- from playful bounces to snappy interactions.

Spring presets
import { springs } from "@/components/ui/animate";

// Available spring presets
springs.bouncy   // { damping: 12, stiffness: 150, mass: 0.5 }
springs.snappy   // { damping: 18, stiffness: 250, mass: 0.8 }
springs.gentle   // { damping: 20, stiffness: 120, mass: 1 }
springs.stiff    // { damping: 26, stiffness: 350, mass: 1 }
springs.default  // { damping: 15, stiffness: 180, mass: 0.8 }

// Use with withSpring
translateX.value = withSpring(targetValue, springs.bouncy);
PropTypeDefault
bouncy
WithSpringConfig
damping: 12, stiffness: 150, mass: 0.5
snappy
WithSpringConfig
damping: 18, stiffness: 250, mass: 0.8
gentle
WithSpringConfig
damping: 20, stiffness: 120, mass: 1
stiff
WithSpringConfig
damping: 26, stiffness: 350, mass: 1
default
WithSpringConfig
damping: 15, stiffness: 180, mass: 0.8

Entering Animations#

Layout animation presets for Animated.View entering. Apply directly to any Reanimated view.

Entering presets
import { entering } from "@/components/ui/animate";
import Animated from "react-native-reanimated";

// All entering presets
<Animated.View entering={entering.fadeIn}>       {/* Simple fade */}
<Animated.View entering={entering.fadeInUp}>     {/* Fade + slide up */}
<Animated.View entering={entering.fadeInDown}>   {/* Fade + slide down */}
<Animated.View entering={entering.slideInUp}>    {/* Slide from bottom */}
<Animated.View entering={entering.slideInDown}>  {/* Slide from top */}
<Animated.View entering={entering.slideInLeft}>  {/* Slide from left */}
<Animated.View entering={entering.slideInRight}> {/* Slide from right */}
<Animated.View entering={entering.zoomIn}>       {/* Scale up */}
<Animated.View entering={entering.bounceIn}>     {/* Bounce entrance */}
<Animated.View entering={entering.flipInX}>      {/* 3D flip */}

Exiting Animations#

Layout animation presets for Animated.View exiting. Pair with entering presets for smooth transitions.

Exiting presets
import { exiting } from "@/components/ui/animate";
import Animated from "react-native-reanimated";

// All exiting presets
<Animated.View exiting={exiting.fadeOut}>       {/* Simple fade out */}
<Animated.View exiting={exiting.fadeOutUp}>     {/* Fade + slide up */}
<Animated.View exiting={exiting.fadeOutDown}>   {/* Fade + slide down */}
<Animated.View exiting={exiting.slideOutUp}>    {/* Slide to top */}
<Animated.View exiting={exiting.slideOutDown}>  {/* Slide to bottom */}
<Animated.View exiting={exiting.zoomOut}>       {/* Scale down */}

Duration & Easing#

Standardized duration constants and easing curves for withTiming. Use these for consistent timing across your app.

Duration & easing
import { duration, easing } from "@/components/ui/animate";
import { withTiming } from "react-native-reanimated";

// Duration presets (ms)
duration.fast    // 150
duration.normal  // 250
duration.slow    // 400
duration.slower  // 600

// Easing presets
easing.easeOut    // Easing.out(Easing.cubic)
easing.easeIn     // Easing.in(Easing.cubic)
easing.easeInOut  // Easing.inOut(Easing.cubic)
easing.spring     // Easing.bezier(0.175, 0.885, 0.32, 1.275)
easing.bounce     // Easing.bounce

// Use together
opacity.value = withTiming(1, {
  duration: duration.normal,
  easing: easing.easeOut,
});

usePressAnimation Hook#

A spring-based scale animation hook for press feedback. Returns an animated style and press handlers to attach to any Pressable.

usePressAnimation
import { usePressAnimation } from "@/components/ui/animate";
import Animated from "react-native-reanimated";

function PressableCard() {
  const { animatedStyle, onPressIn, onPressOut } = usePressAnimation();
  return (
    <Animated.View style={animatedStyle}>
      <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
        <Card>Press me</Card>
      </Pressable>
    </Animated.View>
  );
}

Custom Scale#

Pass a custom scale value (default is 0.96) for more or less dramatic press effects.

Custom scale
// Custom scale value (default is 0.96)
const { animatedStyle, onPressIn, onPressOut } = usePressAnimation(0.92);

Return Values#

PropTypeDefault
animatedStyle
AnimatedStyleProp
-
onPressIn
() => void
-
onPressOut
() => void
-

Stagger Helper#

Use stagger(index, interval) to create staggered entrance animations for list items. The default interval is 50ms.

Stagger children
import { stagger } from "@/components/ui/animate";
import Animated from "react-native-reanimated";

// Stagger children with 50ms intervals
{items.map((item, index) => (
  <Animated.View
    key={item.id}
    entering={FadeInUp.delay(index * 50)}
  >
    <ListItem {...item} />
  </Animated.View>
))}

Usage in Components#

Combine presets, hooks, and springs for rich interactive components.

Combined example
import { entering, exiting, springs, usePressAnimation } from "@/components/ui/animate";
import Animated, { withSpring, useSharedValue, useAnimatedStyle } from "react-native-reanimated";
import { Pressable, View } from "react-native";
import { Card } from "@/components/ui/card";

export function AnimatedCard() {
  const { animatedStyle, onPressIn, onPressOut } = usePressAnimation();
  const offset = useSharedValue(0);

  const slideStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: offset.value }],
  }));

  return (
    <Animated.View
      entering={entering.fadeInUp}
      exiting={exiting.fadeOut}
      style={[animatedStyle, slideStyle]}
    >
      <Pressable
        onPressIn={onPressIn}
        onPressOut={onPressOut}
        onPress={() => {
          offset.value = withSpring(offset.value === 0 ? 50 : 0, springs.snappy);
        }}
      >
        <Card>
          <Text>Tap to slide</Text>
        </Card>
      </Pressable>
    </Animated.View>
  );
}

Accessibility#

  • Animations respect the system Reduce Motion preference when used with Reanimated layout animations.
  • Press animations provide visual feedback for interactive elements.
  • Keep durations short (under 400ms) for responsiveness.

Source#

components/ui/animate.tsx
import { useCallback } from "react";
import {
  Easing,
  FadeIn, FadeOut,
  FadeInUp, FadeInDown, FadeOutUp, FadeOutDown,
  SlideInUp, SlideInDown, SlideInLeft, SlideInRight,
  SlideOutUp, SlideOutDown,
  ZoomIn, ZoomOut,
  BounceIn,
  FlipInXUp,
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming,
  withDelay,
  type WithSpringConfig,
} from "react-native-reanimated";

// ── Spring Presets (iOS-quality physics) ──────────────────────
export const springs: Record<string, WithSpringConfig> = {
  bouncy:  { damping: 12, stiffness: 150, mass: 0.5 },
  snappy:  { damping: 18, stiffness: 250, mass: 0.8 },
  gentle:  { damping: 20, stiffness: 120, mass: 1 },
  stiff:   { damping: 26, stiffness: 350, mass: 1 },
  default: { damping: 15, stiffness: 180, mass: 0.8 },
};

// ── Layout Animation Presets (entering) ───────────────────────
export const entering = {
  fadeIn:       FadeIn.duration(200),
  fadeInUp:     FadeInUp.duration(250).springify().damping(18),
  fadeInDown:   FadeInDown.duration(250).springify().damping(18),
  slideInUp:    SlideInUp.duration(300).springify(),
  slideInDown:  SlideInDown.duration(300).springify(),
  slideInLeft:  SlideInLeft.duration(300).springify(),
  slideInRight: SlideInRight.duration(300).springify(),
  zoomIn:       ZoomIn.duration(250).springify().damping(15),
  bounceIn:     BounceIn.duration(400),
  flipInX:      FlipInXUp.duration(400),
};

// ── Layout Animation Presets (exiting) ────────────────────────
export const exiting = {
  fadeOut:      FadeOut.duration(150),
  fadeOutUp:    FadeOutUp.duration(200),
  fadeOutDown:  FadeOutDown.duration(200),
  slideOutUp:   SlideOutUp.duration(200),
  slideOutDown: SlideOutDown.duration(200),
  zoomOut:      ZoomOut.duration(200),
};

// ── Duration Presets (ms) ─────────────────────────────────────
export const duration = {
  fast:   150,
  normal: 250,
  slow:   400,
  slower: 600,
};

// ── Easing Presets ────────────────────────────────────────────
export const easing = {
  easeOut:   Easing.out(Easing.cubic),
  easeIn:    Easing.in(Easing.cubic),
  easeInOut: Easing.inOut(Easing.cubic),
  spring:    Easing.bezier(0.175, 0.885, 0.32, 1.275),
  bounce:    Easing.bounce,
};

// ── Hooks ─────────────────────────────────────────────────────

/** Spring-based scale for press feedback. Attach onPressIn/onPressOut to Pressable. */
export function usePressAnimation(scale = 0.96) {
  const pressed = useSharedValue(1);
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ scale: pressed.value }],
  }));
  const onPressIn = useCallback(() => {
    pressed.value = withSpring(scale, springs.snappy);
  }, [scale, pressed]);
  const onPressOut = useCallback(() => {
    pressed.value = withSpring(1, springs.snappy);
  }, [pressed]);
  return { animatedStyle, onPressIn, onPressOut };
}

/** Staggered delay for child entrance animations. Returns delay(ms) for index. */
export function stagger(index: number, interval = 50) {
  return withDelay(index * interval, withTiming(1, { duration: duration.normal }));
}