Animations

A screen that just snaps between states feels cheap; a screen that eases, fades, and slides feels polished. React Native ships with a built-in animation system called Animated that handles most of what an app actually needs — fades, slides, scales, rotations — without pulling in any extra library. This page covers Animated in depth, then gives you an honest, conceptual look at the two tools you'll reach for once your animation needs get more advanced: LayoutAnimation and react-native-reanimated.

Animated.Value: A Value React Can Animate Smoothly

Ordinary React state (from useState, covered in State & Hooks) triggers a full re-render every time it changes — great for toggling things on and off, but far too slow to drive 60 frames per second of smooth motion. Animated.Value is a special container built specifically for animation: React Native can update it many times a second, often without re-rendering your component at all.

You almost always create an Animated.Value with useRef rather than useState. The reason is important: useRef gives you a plain object that persists across re-renders without ever causing one itself. If you created the Animated.Value with plain useState instead, a fresh one would be created on every re-render — losing whatever animation progress it had — which defeats the point entirely.

import React, { useRef, useEffect } from 'react';
import { Animated, Text } from 'react-native';

export default function FadeInText() {
  // useRef keeps this SAME Animated.Value alive across every re-render
  const fadeAnim = useRef(new Animated.Value(0)).current;

  // ... animation logic goes here, see next section

  return (
    <Animated.Text style={{ opacity: fadeAnim }}>
      Welcome!
    </Animated.Text>
  );
}

Note the component name: Animated.Text, not plain Text. Only components wrapped by Animated (Animated.View, Animated.Text, Animated.Image, or your own component wrapped with Animated.createAnimatedComponent) know how to accept an Animated.Value directly in their style.

Animating a Value with Animated.timing()

Animated.timing() moves an Animated.Value from its current value to a target toValue over a given duration, using an easing curve. Calling .start() actually kicks the animation off. A common pattern is starting it once, on mount, using useEffect with an empty dependency array.

import React, { useRef, useEffect } from 'react';
import { Animated, Text } from 'react-native';

export default function FadeInText() {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
    }).start();
  }, []); // empty deps: run once, when the component first mounts

  return (
    <Animated.Text style={{ opacity: fadeAnim }}>
      Welcome!
    </Animated.Text>
  );
}

fadeAnim starts at 0 (fully transparent) and, over 500 milliseconds, eases up to 1 (fully opaque). Because fadeAnim is wired directly into opacity, the text visibly fades in on screen — no manual frame-by-frame updates needed.

The Critical useNativeDriver Option

useNativeDriver: true is one of the most important options in the whole Animated API, and it's worth understanding exactly what it does. By default, JavaScript computes every frame of an animation and sends each new value across to the native side — meaning if your JS thread ever gets busy (a heavy render, a big list update, a network callback), the animation can visibly stutter. Setting useNativeDriver: true instead hands the entire animation off to the native side up front, so it plays smoothly on its own thread independent of whatever JavaScript is doing at that moment.

Important limitation: the native driver only supports a specific set of properties — mainly opacity and transform (translate, scale, rotate). It does not support backgroundColor, or layout-affecting properties like width, height, top, or margin — those still animate on the JS thread and need useNativeDriver: false (or the default). Always animate opacity/transform when you can; reach for a layout property only when you must.

Interpolating a Value with .interpolate()

A single Animated.Value often needs to drive something other than a plain 0-to-1 number — a rotation in degrees, a color, a position in pixels. .interpolate() maps an input range onto a different output range, letting one underlying animated value power a completely different-looking effect. A classic example is a full 360-degree spin, driven by the same 0-to-1 value used for the fade above.

import React, { useRef, useEffect } from 'react';
import { Animated, Easing } from 'react-native';

export default function SpinningLoader() {
  const spinAnim = useRef(new Animated.Value(0)).current;

  useEffect(() => {
    Animated.timing(spinAnim, {
      toValue: 1,
      duration: 1000,
      easing: Easing.linear,
      useNativeDriver: true,
    }).start();
  }, []);

  // Map the 0..1 progress value onto a 0deg..360deg rotation
  const rotate = spinAnim.interpolate({
    inputRange: [0, 1],
    outputRange: ['0deg', '360deg'],
  });

  return (
    <Animated.View style={{ transform: [{ rotate }] }}>
      <Text>⟳</Text>
    </Animated.View>
  );
}

inputRange and outputRange must have the same number of entries and both must be in ascending (or both descending) order — interpolate() works for more than just two-point ranges too, letting you map several checkpoints along the animation to different output values.

LayoutAnimation: Animating Layout Changes Automatically

Animated is great when you explicitly drive a value yourself. But sometimes what changes is just your component tree's layout — an item gets added to a list, or removed, and every sibling needs to smoothly slide into its new position rather than snapping there abruptly. LayoutAnimation handles exactly that case with far less code: you call LayoutAnimation.configureNext(...) right before the state update that changes your layout (like removing an item from an array), and React Native automatically animates every resulting layout change for you — no manual Animated.Value per item required.

react-native-reanimated: The Modern, More Powerful Option

Animated covers most everyday needs, but complex, gesture-driven animations — dragging a card and having it follow your finger in real time, chained physics-based motion, animations that must respond instantly to touch without any JS-thread round trip at all — tend to outgrow it. react-native-reanimated is the community's de facto standard library for this tier of animation. It runs animation and gesture logic almost entirely on the native/UI thread using "worklets," giving noticeably smoother results for advanced interactions than the built-in API can comfortably achieve. It's commonly paired with react-native-gesture-handler for swipe/drag/pinch gestures. Treat this section as awareness rather than a how-to — reach for react-native-reanimated once you find yourself fighting the built-in Animated API for a genuinely complex, interactive animation.

Note: every example above is correct, static React Native/JSX code meant to be copied into your own project — this site doesn't run a React Native build, so nothing animates on this page itself.
Next up: see several of these building blocks — styling, forms, and now the concepts here — combined into one real, hands-on UI. Continue to UI Designs: Login Screen.