npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-native-reanimated-modal

v1.2.4

Published

A lightweight and performant modal component. Designed for smooth animations, flexibility, and minimal footprint.

Downloads

475

Readme

documentation npm bundle size

A lightweight, scalable, flexible, and high-performance modal component. Based on the vanilla Modal component for maximum compatibility and native feel. Built with react-native-reanimated and react-native-gesture-handler.

✨ Features

  • 🚀 Performance: Built with react-native-reanimated for 60fps animations that run on the UI thread
  • 🎨 Smooth Animations: Supports fade, slide, and scale animations with customizable configs
  • 👆 Gesture Support: Interactive swipe-to-dismiss in any direction (up, down, left, right)
  • 🪶 Lightweight: Minimal dependencies and smaller bundle size compared to alternatives
  • 📱 Native Feel: Uses React Native's Modal component as foundation for platform consistency
  • 🔧 Flexible: Highly customizable with extensive prop options
  • 📚 TypeScript: Full TypeScript support out of the box
  • 🔄 Multi-Modal: Easy integration with React Navigation and support for multiple overlays

🎮 Example

  1. Install Expo Go on your phone
  2. Scan the QR code with your camera
  3. Open the link in Expo Go
  4. Explore example app!

Or browse the code: 📂 View Example Code →

📚 Documentation

Full API and usage documentation: 🗂️ View Documentation →

📦 Installation

npm install react-native-reanimated-modal
yarn add react-native-reanimated-modal
pnpm add react-native-reanimated-modal
bun add react-native-reanimated-modal

Required Dependencies

This library depends on the following peer dependencies:

  1. react-native-reanimated (>= 3.0.0)
  2. react-native-gesture-handler (>= 2.0.0)
  3. react-native-worklets (>= 0.5.0) - Required for Reanimated 4.0.0+

Note: Make sure to follow the installation guides for all libraries, as they require additional platform-specific setup steps.

🚀 Basic Usage

import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Modal } from 'react-native-reanimated-modal';

const App = () => {
  const [visible, setVisible] = useState(false);

  return (
    <GestureHandlerRootView>
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Button title="Show Modal" onPress={() => setVisible(true)} />

        <Modal visible={visible} onHide={() => setVisible(false)}>
          <View style={{
            backgroundColor: 'white',
            padding: 20,
            borderRadius: 10,
            margin: 20,
          }}>
            <Text>Hello from Modal!</Text>
            <Button title="Close" onPress={() => setVisible(false)} />
          </View>
        </Modal>
      </View>
    </GestureHandlerRootView>
  );
};

📖 API Documentation

New Configuration-Based API (Recommended)

Starting from v1.1.0, we recommend using the new configuration-based API for better type safety and cleaner code:

Animation Configurations

import type { ModalAnimationConfig } from 'react-native-reanimated-modal';

// Scale animation with custom settings
const scaleConfig: ModalAnimationConfig<'scale'> = {
  type: 'scale',
  duration: 400,
  scaleFactor: 0.8, // Start from 80% size
};

// Fade animation
const fadeConfig: ModalAnimationConfig<'fade'> = {
  type: 'fade',
  duration: 300,
};

// Slide animation with complex directions
const slideConfig: ModalAnimationConfig<'slide'> = {
  type: 'slide',
  duration: 500,
  direction: {
    start: 'down',        // Slides in from bottom
    end: ['down', 'right'], // Can dismiss by swiping down or right
  },
};

// Simple slide animation
const simpleSlideConfig: ModalAnimationConfig<'slide'> = {
  type: 'slide',
  duration: 400,
  direction: 'up', // Both slide-in and dismiss direction
};

Swipe Configurations

import type { ModalSwipeConfig } from 'react-native-reanimated-modal';

// Basic swipe config
const basicSwipe: ModalSwipeConfig = {
  enabled: true,
  directions: ['down', 'left', 'right'], // Allow swiping in these directions
  threshold: 120,
};

// Advanced swipe config with custom bounce
const advancedSwipe: ModalSwipeConfig = {
  enabled: true,
  directions: ['up', 'down'], // Only vertical swipes
  threshold: 80,
  bounceSpringConfig: {
    dampingRatio: 0.7,
    duration: 400,
  },
  bounceOpacityThreshold: 0.1,
};

// Disabled swipe
const noSwipe: ModalSwipeConfig = {
  enabled: false,
};

Usage Examples

<Modal
  visible={visible}
  animation={scaleConfig}
  swipe={advancedSwipe}
>
  {/* Your content */}
</Modal>

// Or with inline configs
<Modal
  visible={visible}
  animation={{
    type: 'scale',
    duration: 600,
    scaleFactor: 0.9,
  }}
  swipe={{
    enabled: true,
    threshold: 100,
  }}
>
  {/* Your content */}
</Modal>

// Backdrop examples
<Modal
  visible={visible}
  onBackdropPress={false} // Prevent backdrop from closing modal
  onHide={() => setVisible(false)} // Only programmatic close allowed
>
  {/* Your content */}
</Modal>

<Modal
  visible={visible}
  backdrop={{ color: 'red', opacity: 0.8 }} // Custom backdrop styling
  onBackdropPress={() => console.log('Backdrop pressed!')} // Custom handler
>
  {/* Your content */}
</Modal>

// Legacy string syntax still supported
<Modal
  visible={visible}
  animation="fade" // Equivalent to { type: 'fade', duration: 300 }
>
  {/* Your content */}
</Modal>

Test IDs

You can pass custom testID props to key elements for easier testing:

| Prop | Type | Default | Description | |-------------------|----------|---------------------|---------------------------------------------| | backdropTestID | string | 'modal-backdrop' | testID for the backdrop Pressable | | contentTestID | string | 'modal-content' | testID for the modal content (Animated.View) | | containerTestID | string | 'modal-container' | testID for the root container View |

These props are optional and help you write robust e2e/unit tests.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | visible | boolean | false | Controls the visibility of the modal | | closable | boolean | true | Whether the modal can be closed by user actions | | children | ReactNode | - | Content to render inside the modal | | style | StyleProp<ViewStyle> | - | Style for the modal container | | contentContainerStyle | StyleProp<ViewStyle> | - | Style for the content wrapper |

Configuration Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | animation | ModalAnimationConfigUnion \| ModalAnimation | { type: 'fade', duration: 300 } | Animation configuration object or simple animation type string | | swipe | ModalSwipeConfig \| false | { enabled: true, directions: ['down'], threshold: 100 } | Swipe gesture configuration |

Backdrop Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | backdrop | ModalBackdropConfig \| ReactNode \| false | { enabled: true, color: 'black', opacity: 0.7 } | Backdrop configuration: false (no backdrop), ReactNode for custom backdrop, or config object | | onBackdropPress | (() => void) \| false | - | Callback when backdrop is pressed. Set to false to prevent backdrop from closing the modal |

Other Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | coverScreen | boolean | false | If true, covers entire screen without using native Modal |

Event Props

| Prop | Type | Description | |------|------|-------------| | onShow | () => void | Called when modal appears | | onHide | () => void | Called when modal disappears |

React Native Modal Props

The component also accepts these props from React Native's Modal:

  • hardwareAccelerated (Android)
  • navigationBarTranslucent (Android)
  • statusBarTranslucent (Android)
  • onOrientationChange (iOS)
  • supportedOrientations (iOS)

Constants

The library exports several useful constants for customization:

import {
  DEFAULT_MODAL_ANIMATION_DURATION,    // 300
  DEFAULT_MODAL_SCALE_FACTOR,          // 0.8
  DEFAULT_MODAL_BACKDROP_CONFIG,       // { enabled: true, color: 'black', opacity: 0.7 }
  DEFAULT_MODAL_SWIPE_THRESHOLD,       // 100
  DEFAULT_MODAL_BOUNCE_SPRING_CONFIG,  // { dampingRatio: 0.5, duration: 700 }
  DEFAULT_MODAL_BOUNCE_OPACITY_THRESHOLD, // 0.05
  DEFAULT_MODAL_SWIPE_DIRECTION, // 'down'
} from 'react-native-reanimated-modal';

// Use in your custom configurations
const customAnimationConfig = {
  type: 'scale',
  duration: DEFAULT_MODAL_ANIMATION_DURATION * 2, // 600ms
  scaleFactor: DEFAULT_MODAL_SCALE_FACTOR,         // 0.8
};

const customBackdropConfig = {
  ...DEFAULT_MODAL_BACKDROP_CONFIG,
  color: 'rgba(0, 0, 0, 0.8)', // Darker backdrop
  opacity: 0.9,
};

Types

type SwipeDirection = 'up' | 'down' | 'left' | 'right';
type ModalAnimation = 'fade' | 'slide' | 'scale' | 'custom';

// Animation states for custom worklet functions
type ModalAnimationState =
  | 'opening'   // Modal is opening (progress: 0 -> 1)
  | 'closing'   // Modal is closing (progress: 1 -> 0)
  | 'sliding'   // User is swiping (offsetX, offsetY active)
  | 'bouncing'  // Bounce back after failed swipe

// Custom worklet function type
type ModalAnimatedStyleFunction = (props: {
  animationState: ModalAnimationState | null;
  swipeDirection?: SwipeDirection | null; // Active during 'sliding' state
  progress: number;        // 0-1 during 'opening'/'closing' states
  offsetX: number;         // Pixel offset during 'sliding' state
  offsetY: number;         // Pixel offset during 'sliding' state
  screenWidth: number;     // Device screen width
  screenHeight: number;    // Device screen height
}) => ViewStyle;

// Configuration Types
type ModalAnimationConfig<T extends ModalAnimation> =
  T extends 'fade' ? FadeAnimationConfig :
  T extends 'slide' ? SlideAnimationConfig :
  T extends 'scale' ? ScaleAnimationConfig :
  T extends 'custom' ? CustomAnimationConfig : never;

interface BaseAnimationConfig {
  duration?: number;
  // Custom worklet functions (optional for all animation types)
  contentAnimatedStyle?: ModalAnimatedStyleFunction;
  backdropAnimatedStyle?: ModalAnimatedStyleFunction;
}

interface FadeAnimationConfig extends BaseAnimationConfig {
  type: 'fade';
}

interface SlideAnimationConfig extends BaseAnimationConfig {
  type: 'slide';
  direction?: SwipeDirection | {
    start: SwipeDirection;
    end: SwipeDirection | SwipeDirection[];
  };
}

interface ScaleAnimationConfig extends BaseAnimationConfig {
  type: 'scale';
  scaleFactor?: number; // 0-1, default: 0.8
}

interface CustomAnimationConfig extends BaseAnimationConfig {
  type: 'custom';
  // No preset animation - relies entirely on contentAnimatedStyle
}

interface ModalSwipeConfig {
  enabled?: boolean;
  directions?: SwipeDirection[];
  threshold?: number;
  bounceSpringConfig?: SpringConfig;
  bounceOpacityThreshold?: number;
}

interface ModalBackdropConfig {
  enabled?: boolean;
  color?: string;
  opacity?: number;
}

type ModalAnimationConfigUnion =
  | FadeAnimationConfig
  | SlideAnimationConfig
  | ScaleAnimationConfig
  | CustomAnimationConfig;

🔄 React Navigation support

When using multiple modals simultaneously with @react-navigation/native-stack, you can leverage iOS's FullWindowOverlay for better layering:

import React from 'react';
import { Platform } from 'react-native';
import { FullWindowOverlay } from 'react-native-screens';
import { Modal } from 'react-native-reanimated-modal';

const isIOS = Platform.OS === 'ios';

const withOverlay = (element: React.ReactNode) =>
  isIOS ? <FullWindowOverlay>{element}</FullWindowOverlay> : element;

const MultiModalExample = () => {
  const [firstModalVisible, setFirstModalVisible] = useState(false);
  const [secondModalVisible, setSecondModalVisible] = useState(false);

  return withOverlay(
    <>
      <Modal
        visible={firstModalVisible}
        coverScreen // Important: excludes native Modal usage
        onHide={() => setFirstModalVisible(false)}
      >
        {/* First modal content */}
      </Modal>

      <Modal
        visible={secondModalVisible}
        coverScreen // Important: excludes native Modal usage
        onHide={() => setSecondModalVisible(false)}
      >
        {/* Second modal content */}
      </Modal>
    </>
  );
};

Important: When using multiple modals with FullWindowOverlay, always set coverScreen={true} prop to exclude the usage of React Native's native Modal component and ensure proper layering.

🎨 Advanced Examples

Fade Animation with Custom Duration

<Modal
  visible={visible}
  animation={{
    type: 'fade',
    duration: 400,
  }}
  swipe={{
    directions: ['down', 'right'],
    threshold: 100,
  }}
  onHide={() => setVisible(false)}
>
  {/* Modal content */}
</Modal>

Scale Animation with Custom Duration

<Modal
  visible={visible}
  animation={{
    type: 'scale',
    duration: 400,
    scaleFactor: 0.8,
  }}
  swipe={{
    directions: ['down', 'right'],
    threshold: 100,
  }}
  onHide={() => setVisible(false)}
>
  {/* Modal content */}
</Modal>

Custom Slide Animation with Swipe Directions

<Modal
  visible={visible}
  animation={{
    type: 'slide',
    duration: 500,
    direction: {
      start: 'down',           // Slides in from bottom
      end: ['down', 'right'],  // Can dismiss by swiping down or right
    },
  }}
  swipe={{
    threshold: 150,
    bounceSpringConfig: {
      dampingRatio: 0.8,
      duration: 400,
    },
  }}
  onHide={() => setVisible(false)}
>
  {/* Modal content */}
</Modal>

Note: When using slide animation with complex directions, the start property determines the initial slide-in direction, while the end property (array or single direction) defines the available swipe-to-dismiss directions.

Full Screen Modal

<Modal
  visible={visible}
  contentContainerStyle={{ flex: 1 }}
  animation={{
    type: 'slide',
    duration: 300,
    direction: 'down',
  }}
  swipe={{
    directions: ['down'],
    threshold: 80,
  }}
  backdrop={false} // No backdrop for full screen
  onHide={() => setVisible(false)}
>
  {/* Modal content */}
</Modal>

Custom Animation Worklets

You can create fully custom animations by providing worklet functions that run on the UI thread. These functions receive animation state information and return style objects that get merged with the default preset animations.

// Custom slide animation with rotation and color changes
<Modal
  visible={visible}
  animation={{
    type: 'slide',
    duration: 500,
    direction: 'down',
    contentAnimatedStyle: ({ progress, animationState }) => {
      'worklet';
      return {
        // Add rotation during slide animation
        transform: [{ rotate: `${progress * 360}deg` }],
        // Change background color based on progress
        backgroundColor: `rgba(255, 0, 0, ${progress * 0.5})`,
      };
    },
    backdropAnimatedStyle: ({ progress }) => {
      'worklet';
      return {
        // Custom backdrop with color transition
        backgroundColor: `rgba(0, 255, 0, ${progress * 0.3})`,
      };
    }
  }}
>
  {/* Modal content */}
</Modal>

// Custom animation type without any presets
<Modal
  visible={visible}
  animation={{
    type: 'custom',
    duration: 800,
    contentAnimatedStyle: ({ progress, offsetX, offsetY, animationState }) => {
      'worklet';

      if (animationState === 'sliding') {
        // During swipe gestures
        return {
          opacity: 1 - Math.abs(offsetX) / 200,
          transform: [
            { translateX: offsetX },
            { scale: 1 - Math.abs(offsetX) / 400 }
          ],
        };
      }

      // During open/close animations
      return {
        opacity: progress,
        transform: [
          { scale: 0.5 + progress * 0.5 },
          { rotateY: `${(1 - progress) * 180}deg` }
        ],
      };
    }
  }}
>
  {/* Modal content */}
</Modal>

// Enhance existing animations with custom effects
<Modal
  visible={visible}
  animation={{
    type: 'fade', // Use fade preset
    duration: 600,
    contentAnimatedStyle: ({ progress }) => {
      'worklet';
      // Add elastic scale effect to fade animation
      const elasticScale = 1 + Math.sin(progress * Math.PI * 2) * 0.1;
      return {
        transform: [{ scale: elasticScale }]
      };
    }
  }}
>
  {/* Modal content */}
</Modal>

Custom Backdrop Examples

// Disable backdrop completely
<Modal visible={visible} backdrop={false}>
  {/* Modal content */}
</Modal>

// Custom backdrop configuration
<Modal
  visible={visible}
  backdrop={{
    enabled: true,
    color: 'rgba(255, 0, 0, 0.3)',
    opacity: 0.8
  }}
>
  {/* Modal content */}
</Modal>

// Custom backdrop renderer (e.g., BlurView)
<Modal
  visible={visible}
  backdrop={
    <BlurView
      style={StyleSheet.absoluteFill}
      blurType="light"
      blurAmount={10}
    />
  }
>
  {/* Modal content */}
</Modal>

🤝 Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

📄 License

MIT


Made with create-react-native-library