WellAlly Logo
WellAlly康心伴
Development

Build a Mindful Breathing App with React Native and Reanimated

Learn how to create a smooth, calming breathing exercise app using React Native and Reanimated. This step-by-step tutorial covers essential animation hooks, sequencing, and UI synchronization for a high-performance, native feel in your mobile wellness applications.

W
2025-12-12
8 min read

In a world filled with constant notifications and distractions, taking a moment to focus on our breath can be a powerful tool for grounding ourselves. Mindful breathing exercises have been shown to reduce stress and improve focus, making them a popular feature in wellness and mental health apps.

This tutorial will guide you through building a simple yet elegant mindful breathing exercise app using React Native and the powerful react-native-reanimated library. We'll focus on creating a visually calming and responsive user experience with smooth animations that are crucial for an app of this nature. A jittery or unresponsive animation can defeat the purpose of a relaxation app, which is why Reanimated is the perfect tool for this job. It allows us to run our animations on the native UI thread, bypassing the JavaScript bridge to achieve buttery-smooth, 60 FPS animations.

By the end of this tutorial, you'll have a functional breathing app and a solid understanding of how to leverage Reanimated to create high-performance animations in your own React Native projects.

Prerequisites

  • A working knowledge of React Native and JavaScript.
  • Node.js and npm/yarn installed on your machine.
  • A React Native development environment set up (Expo Go or React Native CLI).
  • Familiarity with React Hooks.

Understanding the Problem

The core challenge in creating a breathing app is to provide clear, calming visual feedback to the user, guiding them through the breathing cycle (inhale, hold, exhale). This requires:

  • A smooth, continuous animation: A circle that expands and contracts to represent breathing in and out.
  • Synchronized text instructions: Text that updates in time with the animation to guide the user.
  • A distraction-free UI: A minimalist design that keeps the user focused on the exercise.

React Native's built-in Animated API can be a good starting point for animations, but for gesture-based interactions and animations that need to be highly performant, react-native-reanimated is the superior choice. It provides a more declarative API and runs animations on the UI thread, preventing them from being interrupted by JavaScript thread operations.

Prerequisites

Before we start, make sure you have a new React Native project. We'll be using Expo for this tutorial for a quicker setup.

code
npx create-expo-app mindful-breathing-app
cd mindful-breathing-app
Code collapsed

Next, install react-native-reanimated:

code
npx expo install react-native-reanimated
Code collapsed

Finally, add the reanimated plugin to your babel.config.js:

code
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['react-native-reanimated/plugin'],
  };
};
Code collapsed

Now, we're ready to start building!

Step 1: Building the Basic UI

First, let's create the basic visual elements: a circle that will expand and contract, and a text element for instructions.

What we're doing

We'll create a simple, centered layout with a circle and a text component.

Implementation

code
// App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.circle}>
        <Text style={styles.text}>Breathe</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000',
    alignItems: 'center',
    justifyContent: 'center',
  },
  circle: {
    width: 200,
    height: 200,
    borderRadius: 100,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 24,
    fontWeight: 'bold',
  },
});
Code collapsed

How it works

This is a standard React Native component that renders a white circle with the text "Breathe" in the center of a black screen. This gives us the basic structure for our animation.

Step 2: Creating the Breathing Animation

Now for the core of our app: the breathing animation. We'll use Reanimated's hooks to create a smooth, looping animation.

What we're doing

We'll use useSharedValue to create an animatable value that will control the scale of the circle. Then, we'll use useAnimatedStyle to apply this animated value to the circle's style. Finally, we'll use withRepeat and withSequence to create a continuous breathing cycle.

Implementation

code
// App.js
import React, { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withRepeat,
  withSequence,
} from 'react-native-reanimated';

export default function App() {
  const scale = useSharedValue(1);

  useEffect(() => {
    scale.value = withRepeat(
      withSequence(
        withTiming(1.5, { duration: 4000 }),
        withTiming(1, { duration: 4000 })
      ),
      -1,
      true
    );
  }, []);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: scale.value }],
    };
  });

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.circle, animatedStyle]}>
        <Text style={styles.text}>Breathe</Text>
      </Animated.View>
    </View>
  );
}

// Styles remain the same
Code collapsed

How it works

  1. useSharedValue(1): We initialize a shared value scale with a value of 1. Shared values are animatable and can be accessed and modified from both the UI and JavaScript threads.
  2. useEffect: We use a useEffect hook to start the animation when the component mounts.
  3. withRepeat(...): This function creates a looping animation. We set the second argument to -1 to make it repeat indefinitely, and the third argument to true to make it reverse direction on each repeat (boomerang effect).
  4. withSequence(...): We define the animation sequence. First, the circle scales up to 1.5 over 4 seconds (withTiming(1.5, { duration: 4000 })), then it scales back down to 1 over 4 seconds (withTiming(1, { duration: 4000 })).
  5. useAnimatedStyle(...): This hook creates an animated style object. It takes a worklet (a function that can run on the UI thread) that returns a style object.
  6. transform: [{ scale: scale.value }]: We link the scale shared value to the transform property of the circle's style.
  7. Animated.View: We wrap our circle View in an Animated.View to make it animatable.

Step 3: Synchronizing Instructional Text

A visual cue is great, but instructional text makes the exercise much clearer. Let's add text that changes in sync with our animation.

What we're doing

We'll create a new shared value to hold the current instruction text. We'll then use runOnJS to update this text from the UI thread, keeping it in sync with the breathing animation.

Implementation

code
// App.js
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withRepeat,
  withSequence,
  runOnJS,
} from 'react-native-reanimated';

export default function App() {
  const scale = useSharedValue(1);
  const [instruction, setInstruction] = useState('Breathe');

  const updateInstruction = (text) => {
    setInstruction(text);
  };

  useEffect(() => {
    scale.value = withRepeat(
      withSequence(
        withTiming(1.5, { duration: 4000 }, () => {
          runOnJS(updateInstruction)('Hold');
        }),
        withTiming(1, { duration: 4000 }, () => {
          runOnJS(updateInstruction)('Breathe');
        })
      ),
      -1,
      true
    );
  }, []);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: scale.value }],
    };
  });

  return (
    <View style={styles.container}>
      <Animated.View style={[styles.circle, animatedStyle]}>
        <Text style={styles.text}>{instruction}</Text>
      </Animated.View>
    </View>
  );
}
// Styles remain the same
Code collapsed

How it works

  1. useState: We use a state variable instruction to hold the text that will be displayed.
  2. runOnJS: Reanimated animations run on the UI thread, which cannot directly update React state. runOnJS allows us to call a JavaScript function (in this case, updateInstruction) from the UI thread.
  3. Animation Callbacks: withTiming accepts an optional callback function that is executed when the animation completes. We use this to call runOnJS(updateInstruction) with the next instruction at the appropriate time in the animation sequence.

Putting It All Together

Here is the complete code for our mindful breathing app:

code
// App.js
import React, { useState, useEffect } from 'react';
import { StyleSheet, Text, View, StatusBar } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withRepeat,
  withSequence,
  runOnJS,
} from 'react-native-reanimated';

export default function App() {
  const scale = useSharedValue(1);
  const [instruction, setInstruction] = useState('Inhale');

  const updateInstruction = (text) => {
    'worklet';
    runOnJS(setInstruction)(text);
  };

  useEffect(() => {
    scale.value = withRepeat(
      withSequence(
        withTiming(1.5, { duration: 4000 }, () => {
          updateInstruction('Hold');
        }),
        withTiming(1.5, { duration: 2000 }, () => {
          updateInstruction('Exhale');
        }),
        withTiming(1, { duration: 4000 }, () => {
          updateInstruction('Hold');
        }),
        withTiming(1, { duration: 2000 }, () => {
            updateInstruction('Inhale');
        })
      ),
      -1,
      false
    );
  }, []);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: scale.value }],
    };
  });

  return (
    <View style={styles.container}>
      <StatusBar barStyle="light-content" />
      <Animated.View style={[styles.circle, animatedStyle]}>
        <Text style={styles.text}>{instruction}</Text>
      </Animated.View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000',
    alignItems: 'center',
    justifyContent: 'center',
  },
  circle: {
    width: 200,
    height: 200,
    borderRadius: 100,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 24,
    fontWeight: 'bold',
  },
});
Code collapsed

Conclusion

We've successfully built a mindful breathing exercise app with a smooth, calming animation using React Native and Reanimated. We've learned how to use Reanimated's core hooks to create complex, looping animations and how to synchronize them with state updates on the JavaScript thread.

This is just the beginning. You can expand on this project by:

  • Allowing the user to set the duration of the breathing exercise.
  • Adding sound effects or haptic feedback.
  • Creating different breathing patterns (e.g., box breathing).

Reanimated is an incredibly powerful library that can take your React Native app's user experience to the next level. I encourage you to explore its documentation and experiment with different animations.

Resources

#

Article Tags

reactnative
animation
mobiledev
healthtech
W

WellAlly's core development team, comprised of healthcare professionals, software engineers, and UX designers committed to revolutionizing digital health management.

Expertise

Healthcare Technology
Software Development
User Experience
AI & Machine Learning

Found this article helpful?

Try KangXinBan and start your health management journey