Beautiful iOS-like sheet transitions for React Native, Expo Go, and Web. Provides smooth, native-feeling custom modal transitions with gesture-based interactions.
- π Smooth scale transitions
- π Gesture-based dismissal with haptic feedback
- π± iOS-like modal animations
- π¨ Customizable animations
- π Two animation modes: incremental & decremental
- π― Border radius sync with gestures
- π Opacity animations
- π Multi-directional dragging support
β οΈ Work in Progress: This package is under active development and not yet recommended for production use. Breaking changes may occur frequently. Feel free to contribute by submitting PRs or reporting issues!
npm install react-native-sheet-transitions
# or
yarn add react-native-sheet-transitions
# or
bun add react-native-sheet-transitions
npm install react-native-reanimated react-native-gesture-handler
- Wrap your app with
SheetProvider
:
import { SheetProvider } from 'react-native-sheet-transitions'
export default function App() {
return (
<SheetProvider>
<Navigation />
</SheetProvider>
)
}
- Use
SheetScreen
in your modal:
import { SheetScreen } from 'react-native-sheet-transitions'
export default function ModalScreen() {
const router = useRouter()
return (
<SheetScreen
onClose={() => router.back()}
dragDirections={{ toBottom: true }}
opacityOnGestureMove={true}
containerRadiusSync={true}
>
<YourContent />
</SheetScreen>
)
}
Prop | Type | Default | Description |
---|---|---|---|
springConfig |
SpringConfig |
{ damping: 15, stiffness: 150, mass: 0.5 } |
Spring animation configuration |
resizeType |
'incremental' | 'decremental' |
'decremental' |
Scale animation mode |
enableForWeb |
boolean |
false |
Enable animations on web platform (not recommended) |
Prop | Type | Default | Description |
---|---|---|---|
onClose |
() => void |
required | Callback when sheet is dismissed |
scaleFactor |
number |
0.83 |
Scale factor for background content |
dragThreshold |
number |
150 |
Distance required to trigger dismiss |
springConfig |
SpringConfig |
{ damping: 15, stiffness: 60, mass: 0.6, restDisplacementThreshold: 0.01, restSpeedThreshold: 0.01 } |
Spring animation config |
dragDirections |
DragDirections |
{ toBottom: true } |
Enabled drag directions |
isScrollable |
boolean |
false |
Enable scroll handling for content |
opacityOnGestureMove |
boolean |
false |
Enable opacity animation during drag |
containerRadiusSync |
boolean |
true |
Sync border radius with drag |
initialBorderRadius |
number |
50 |
Initial border radius value |
style |
ViewStyle |
undefined | Additional container styles |
disableSyncScaleOnDragDown |
boolean |
false |
Disable scale sync during drag |
customBackground |
ReactNode |
undefined | Custom background component with fade animation |
onOpenStart |
() => void |
undefined | Called when sheet starts opening animation |
onOpenEnd |
() => void |
undefined | Called when sheet opening animation completes |
onCloseStart |
() => void |
undefined | Called when user gesture triggers close |
onCloseEnd |
() => void |
undefined | Called when close animation completes (replaces onClose if provided) |
onBelowThreshold |
() => void |
undefined | Called when drag goes below threshold after exceeding it |
disableRootScale |
boolean |
false |
Disable background scaling effect |
disableSheetContentResizeOnDragDown |
boolean |
false |
Disable sheet content scaling during drag down |
interface SpringConfig {
damping?: number
stiffness?: number
mass?: number
velocity?: number
}
interface DragDirections {
toTop?: boolean
toBottom?: boolean
toLeft?: boolean
toRight?: boolean
}
Background scales up instead of down:
<SheetProvider resizeType="incremental">
<App />
</SheetProvider>
<SheetScreen
springConfig={{
damping: 15,
stiffness: 120,
mass: 0.8
}}
scaleFactor={0.85}
dragThreshold={100}
opacityOnGestureMove={true}
containerRadiusSync={true}
initialBorderRadius={40}
>
<Content />
</SheetScreen>
<SheetScreen
dragDirections={{
toBottom: true,
toLeft: true,
toRight: true
}}
onClose={handleClose}
>
<Content />
</SheetScreen>
You can add a custom background component that fades in/out with the modal:
import { BlurView } from 'expo-blur'
<SheetScreen
customBackground={
<BlurView
intensity={20}
style={StyleSheet.absoluteFill}
/>
}
onClose={handleClose}
>
<Content />
</SheetScreen>
The background component will:
- Fade in when modal opens
- Fade out when modal closes
- Be positioned absolutely behind the modal content
- Not sync opacity with drag gestures
Configure your modal screen:
// app/_layout.tsx
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>
)
}
Pull requests are welcome! For major changes:
- Fork the repository
- Create your feature branch
- Commit your changes
- Push to the branch
- Open a pull request
The SheetScreen component provides several callbacks for precise control:
interface SheetScreenProps {
// Called when sheet starts opening animation
onOpenStart?: () => void
// Called when sheet opening animation completes
onOpenEnd?: () => void
// Called when user drags above threshold
onCloseStart?: () => void
// Called when user drags below threshold
onBelowThreshold?: () => void
// Called when close animation completes
onCloseEnd?: () => void
}
<SheetScreen
onCloseStart={() => {
// Warn user they can release to close
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning)
}}
onBelowThreshold={() => {
// Reset state when going below threshold
}}
onCloseEnd={() => {
// Success haptic when sheet closes
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success)
router.back()
}}
>
{/* content */}
</SheetScreen>
The background scaling effect (where the previous screen scales down when the sheet opens) is:
- β Enabled by default on iOS
- β Disabled by default on Android and Web
- Can be disabled on iOS using
disableRootScale={true}
<SheetScreen
disableRootScale={true} // Disable background scaling even on iOS
onClose={handleClose}
>
<Content />
</SheetScreen>
By default, sheet transitions are disabled on web platforms for better UX and accessibility. Web modals should follow web platform conventions.
If you need to enable animations on web:
<SheetProvider enableForWeb={true}>
<App />
</SheetProvider>
Note: Enabling sheet transitions on web is not recommended as it can:
- Interfere with native web accessibility features
- Create inconsistent UX across different browsers
- Impact performance on lower-end devices
- Break expected web modal behaviors
Consider using native web modals or dialogs for better user experience on web platforms.
- Fix Scrolling gesture handling and momentum issues
- Multiple Portal support for nested sheets
- iOS-like sheet detents (snap points)
- Configurable small, medium, large positions
- Custom snap point values
- Default snap point configuration
- Smooth animations between detents
- Gesture-based snapping behavior
- Enhanced gesture controls
- Velocity-based dismissal
- Directional lock
- Accessibility improvements
- Screen reader support
- Reduced motion preferences
- Focus support
- More animations
- Shared element transitions
- Better web support
- Keyboard navigation
- Focus trapping
MIT Β© saulamsal