Description:
React Native Sheet Transitions is a modal library that recreates gesture-based, iOS-inspired sheet animations for React Native, Expo Go, and web platforms.
The library handles scale transformations, border radius synchronization, and drag-based dismissal with haptic feedback. You can customize animation timing, drag directions, and background effects through a declarative API.
It integrates with React Native Reanimated and React Native Gesture Handler to deliver frame-accurate animations. You control sheet behavior through provider configuration and component props without managing animation state manually.
Features
- 🔄 Scale Transitions: Background content scales down or up when sheets appear, matching native iOS behavior
- 👆 Gesture Dismissal: You drag sheets in multiple directions with haptic feedback at key interaction points
- 📱 iOS-like Animations: Spring-based timing curves reproduce the feel of UIKit modal presentations
- 🎨 Customizable Config: You adjust spring physics, scale factors, and drag thresholds per sheet instance
- 🌗 Animation Modes: Choose between incremental scaling (background grows) or decremental scaling (background shrinks)
- 🎯 Border Radius Sync: Corner radius animates in sync with drag position for smooth visual continuity
- 🔍 Opacity Control: Sheet opacity responds to drag gestures with configurable fade behavior
- 📐 Multi-Directional Drag: Enable dismissal from bottom, top, left, or right based on interaction patterns
Use Cases
- Bottom Sheets: Implement interactive content panels that slide from the bottom.
- Settings Panels: Create animated configuration menus with smooth transitions.
- Detail Views: Present additional information with gesture-controlled dismissal.
- Action Sheets: Build iOS-style action selectors with haptic feedback.
How to Use It
1. Install the core package and peer dependencies with a package manager you prefer:
npm install react-native-sheet-transitions react-native-reanimated react-native-gesture-handler2. Wrap your application root with SheetProvider to initialize animation context:
The provider accepts global spring configuration and resize mode. All sheet instances inherit these defaults unless overridden locally.
import { SheetProvider } from 'react-native-sheet-transitions';
export default function App() {
return (
<SheetProvider
springConfig={{ damping: 15, stiffness: 150, mass: 0.5 }}
resizeType="decremental"
>
<Navigation />
</SheetProvider>
);
}3. Create a basic modal using SheetScreen component:
The
onClosecallback fires when the sheet completes its dismiss animation. You handle navigation or state cleanup in this function.
import { SheetScreen } from 'react-native-sheet-transitions';
import { useRouter } from 'expo-router';
export default function ModalScreen() {
const router = useRouter();
return (
<SheetScreen
onClose={() => router.back()}
dragDirections={{ toBottom: true }}
opacityOnGestureMove={true}
containerRadiusSync={true}
>
<View style={{ flex: 1, padding: 20 }}>
<Text>Sheet Content</Text>
</View>
</SheetScreen>
);
}4. Expo Router Integration. Configure modal presentation in your layout file:
The transparent modal presentation prevents Expo Router from rendering default backgrounds that interfere with scale animations.
import { Stack } from 'expo-router';
import { SheetProvider } from 'react-native-sheet-transitions';
export default function Layout() {
return (
<SheetProvider>
<Stack>
<Stack.Screen name="index" />
<Stack.Screen
name="modal"
options={{
presentation: 'transparentModal',
contentStyle: { backgroundColor: 'transparent' }
}}
/>
</Stack>
</SheetProvider>
);
}5. Custom Animation Configuration. Override default spring physics and interaction thresholds:
Lower stiffness values create slower, more elastic animations. Higher mass increases momentum during gesture interactions.
<SheetScreen
springConfig={{
damping: 15,
stiffness: 120,
mass: 0.8
}}
scaleFactor={0.85}
dragThreshold={100}
opacityOnGestureMove={true}
containerRadiusSync={true}
initialBorderRadius={40}
onClose={handleClose}
>
<Content />
</SheetScreen>6. Enable drag gestures from multiple edges:
You activate multiple directions for interfaces where users expect lateral swipe dismissal alongside vertical gestures.
<SheetScreen
dragDirections={{
toBottom: true,
toLeft: true,
toRight: true
}}
onClose={handleClose}
>
<Content />
</SheetScreen>7. Add blur or color overlays that fade with the sheet:
The background component receives automatic fade animations during open and close transitions. It positions absolutely behind sheet content without requiring manual layout calculations.
import { BlurView } from 'expo-blur';
import { StyleSheet } from 'react-native';
<SheetScreen
customBackground={
<BlurView
intensity={20}
style={StyleSheet.absoluteFill}
/>
}
onClose={handleClose}
>
<Content />
</SheetScreen>8. Hook into animation phases for state management or haptic feedback:
The
onCloseStartcallback fires when drag distance exceeds the dismiss threshold. UseonBelowThresholdto detect when users drag past the threshold but release before completing dismissal.
import * as Haptics from 'expo-haptics';
<SheetScreen
onOpenStart={() => {
console.log('Sheet animation started');
}}
onOpenEnd={() => {
console.log('Sheet fully visible');
}}
onCloseStart={() => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
}}
onBelowThreshold={() => {
console.log('User released before threshold');
}}
onCloseEnd={() => {
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
router.back();
}}
>
<Content />
</SheetScreen>9. Configure background to scale up instead of down:
Incremental mode expands the background content as the sheet appears, creating an alternative visual style for certain design systems.
<SheetProvider resizeType="incremental">
<App />
</SheetProvider>10. Turn off scale effects on iOS or force disable on all platforms:
Background scaling runs by default on iOS only. Android and web skip this effect unless you override platform detection.
<SheetScreen
disableRootScale={true}
onClose={handleClose}
>
<Content />
</SheetScreen>11. Enable scroll detection for sheets containing long content:
The
isScrollableprop prevents gesture conflicts between sheet drag and content scroll when users interact with scrollable regions.
<SheetScreen
isScrollable={true}
dragDirections={{ toBottom: true }}
onClose={handleClose}
>
<ScrollView>
<LongContent />
</ScrollView>
</SheetScreen>API Reference
SheetProvider Props
| Prop | Type | Default | Description |
|---|---|---|---|
springConfig | SpringConfig | { damping: 15, stiffness: 150, mass: 0.5 } | Configuration for the spring animation physics. |
resizeType | 'incremental' | 'decremental' | 'decremental' | Determines if the background scales up or down. |
enableForWeb | boolean | false | Forces animations on web platforms (not recommended). |
SheetScreen Props
| Prop | Type | Default | Description |
|---|---|---|---|
onClose | () => void | Required | Function called when the sheet is dismissed. |
scaleFactor | number | 0.83 | The scale value for the background content. |
dragThreshold | number | 150 | Pixel distance required to trigger a dismiss action. |
springConfig | SpringConfig | (See Docs) | Overrides the default spring configuration. |
dragDirections | DragDirections | { toBottom: true } | Specifies which directions trigger drag gestures. |
isScrollable | boolean | false | Enables scroll handling within the sheet content. |
opacityOnGestureMove | boolean | false | Changes opacity during drag interactions. |
containerRadiusSync | boolean | true | Syncs the border radius with the drag position. |
initialBorderRadius | number | 50 | The starting border radius value. |
style | ViewStyle | undefined | Additional styles for the container. |
disableSyncScaleOnDragDown | boolean | false | Stops scale synchronization during downward drags. |
customBackground | ReactNode | undefined | Component that fades in behind the modal. |
onOpenStart | () => void | undefined | Triggered when the opening animation begins. |
onOpenEnd | () => void | undefined | Triggered when the opening animation finishes. |
onCloseStart | () => void | undefined | Triggered when a user gesture initiates a close. |
onCloseEnd | () => void | undefined | Triggered when the close animation finishes. |
onBelowThreshold | () => void | undefined | Triggered when drag returns below the threshold. |
disableRootScale | boolean | false | Disables the background scaling effect entirely. |
disableSheetContentResizeOnDragDown | boolean | false | Prevents sheet content from resizing during drag. |
Related Resources
- React Native Reanimated: Powers the animation engine with worklet-based transforms and gesture synchronization
- React Native Gesture Handler: Handles native touch events and pan gesture recognition for sheet interactions
- Expo Router: Manages file-based navigation and modal presentation configuration for Expo apps
- React Native Bottom Sheet: Alternative sheet implementation with snap points and scrollable content support
FAQs
Q: Does this library work with React Navigation?
A: Yes. Configure transparent modal presentation in your stack navigator and wrap the app with SheetProvider. The library operates independently of navigation systems.
Q: How do I prevent accidental dismissal on fast scrolls?
A: Set isScrollable={true} and increase dragThreshold to values like 200 or higher. This separates content scroll from sheet dismiss gestures.
Q: Can I disable animations on specific platforms?
A: Use disableRootScale={true} to disable background scaling. For full animation disable, conditionally render standard modals instead of SheetScreen on target platforms.
Q: Why does the sheet feel sluggish on Android?
A: Lower the mass value in springConfig and increase stiffness. Android performs best with damping 12, stiffness 180, mass 0.4.
Q: How do I customize the dismiss threshold per sheet?
A: Pass a different dragThreshold prop to each SheetScreen instance. Values between 100 and 200 work for most interaction patterns.