https://github.com/sergeymild/react-native-sheet
A performant interactive bottom sheet for React Native
https://github.com/sergeymild/react-native-sheet
android bottomsheet ios react-native
Last synced: 6 months ago
JSON representation
A performant interactive bottom sheet for React Native
- Host: GitHub
- URL: https://github.com/sergeymild/react-native-sheet
- Owner: sergeymild
- License: mit
- Created: 2023-02-22T04:34:46.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2026-01-15T03:43:12.000Z (6 months ago)
- Last Synced: 2026-01-15T09:42:52.510Z (6 months ago)
- Topics: android, bottomsheet, ios, react-native
- Language: TypeScript
- Homepage:
- Size: 7.08 MB
- Stars: 1
- Watchers: 1
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# react-native-sheet

A high-performance, native bottom sheet component for React Native with auto-sizing and Fabric architecture support.
## Features
- **Native Implementation** - Built with Fabric architecture for iOS and modern Android APIs
- **Auto-sizing** - Automatically sizes to content height with dynamic updates
- **Landscape/Portrait Support** - Different constraints for device orientations
- **Data Passing** - Pass data to sheets and receive values on dismiss
- **Multiple Sheets** - Support for stacked sheets with named identifiers
- **Dual APIs** - Both imperative and declarative approaches
- **Global Sheet API** - Show sheets without pre-declaring components
- **Customizable** - Control corner radius, colors, sizing constraints
- **Dismissal Control** - Optional prevention of outside tap dismissal
- **ScrollView Integration** - Special handling for scrollable content
## Installation
```sh
//package.json
"react-native-sheet":"sergeymild/react-native-sheet#7.0.0"
yarn
```
## Usage
### Setup
Wrap your app with `SheetProvider`:
```tsx
import { SheetProvider } from 'react-native-sheet';
export default function App() {
return (
);
}
```
### Basic Example
```tsx
import { FittedSheet, type FittedSheetRef } from 'react-native-sheet';
import { useRef } from 'react';
import { View, Text, Button } from 'react-native';
export const BasicExample = () => {
const sheetRef = useRef(null);
return (
sheetRef.current?.show()}
/>
Hello from Bottom Sheet!
sheetRef.current?.hide()}
/>
);
};
```
### Named Sheets (Imperative API)
```tsx
import {
FittedSheet,
presentFittedSheet,
dismissFittedSheet
} from 'react-native-sheet';
export const NamedExample = () => {
return (
<>
presentFittedSheet('mySheet')}
/>
Named Sheet
dismissFittedSheet('mySheet')}
/>
>
);
};
```
### Global Sheet (Imperative API without Component Declaration)
For scenarios where you need to show a sheet without pre-declaring a `FittedSheet` component, you can use the global sheet API:
```tsx
import {
presentGlobalFittedSheet,
dismissGlobalFittedSheet
} from 'react-native-sheet';
import { View, Text, Button } from 'react-native';
export const GlobalSimpleUsage = () => {
return (
{
presentGlobalFittedSheet({
name: 'myGlobalSheet',
onDismiss: () => {
console.log('Sheet dismissed');
},
sheetProps: {
params: {
backgroundColor: 'white',
topLeftRightCornerRadius: 10,
},
rootViewStyle: {
paddingBottom: 56,
},
},
children: (
Global Sheet Content
dismissGlobalFittedSheet('myGlobalSheet')}
/>
),
});
}}
/>
{/* Dismiss from outside */}
dismissGlobalFittedSheet('myGlobalSheet')}
/>
);
};
```
**Setup**: To enable the global sheet, add the `addGlobalSheetView` prop to `SheetProvider`:
```tsx
import { SheetProvider } from 'react-native-sheet';
export default function App() {
return (
);
}
```
You can also provide default sheet properties that will be used for all global sheets:
```tsx
```
#### Advanced Global Sheet Usage
**Multiple Global Sheets:**
You can present multiple global sheets simultaneously by using unique names:
```tsx
import {
presentGlobalFittedSheet,
dismissGlobalFittedSheet
} from 'react-native-sheet';
// Present first sheet
presentGlobalFittedSheet({
name: 'sheet1',
sheetProps: {
params: { backgroundColor: 'white' }
},
children: (
First Sheet
{
presentGlobalFittedSheet({
name: 'sheet2',
sheetProps: {
params: { backgroundColor: 'lightblue' }
},
children: (
Second Sheet
dismissGlobalFittedSheet('sheet2')}
/>
dismissGlobalFittedSheet('sheet1')}
/>
),
});
}}
/>
),
});
```
**Dynamic ScrollView in Global Sheet:**
When your sheet content loads asynchronously and includes a ScrollView, you need to attach the scrollview after it renders:
```tsx
import {
presentGlobalFittedSheet,
attachScrollViewToGlobalFittedSheet
} from 'react-native-sheet';
import { useEffect, useState } from 'react';
import { ScrollView, ActivityIndicator } from 'react-native';
const DynamicContent = ({ sheetName }) => {
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
// Simulate async data loading
setTimeout(() => {
setData(Array.from({ length: 20 }, (_, i) => i + 1));
setLoading(false);
// Attach ScrollView after it appears
setTimeout(() => {
attachScrollViewToGlobalFittedSheet(sheetName);
}, 100);
}, 2000);
}, [sheetName]);
if (loading) {
return (
Loading data...
);
}
return (
Scrollable Content
{data.map(item => (
Item {item}
))}
);
};
// Present the sheet
presentGlobalFittedSheet({
name: 'dynamicSheet',
sheetProps: {
params: {
backgroundColor: 'white',
topLeftRightCornerRadius: 10,
},
},
children: ,
});
```
**Dismissing Multiple Sheets:**
```tsx
// Dismiss all global sheets at once
dismissGlobalFittedSheet('sheet1');
dismissGlobalFittedSheet('sheet2');
dismissGlobalFittedSheet('sheet3');
```
**Passing Data to Global Sheets:**
Since global sheets don't support the `show(data)` / `hide(returnValue)` pattern, you can pass data using these approaches:
```tsx
// 1. Pass data via closure/variables
const userId = 123;
const userName = 'John Doe';
presentGlobalFittedSheet({
name: 'userProfile',
children: (
User ID: {userId}
Name: {userName}
),
});
// 2. Return data via closure in onDismiss callback
let result = null;
presentGlobalFittedSheet({
name: 'confirmDialog',
onDismiss: () => {
// Handle the result here
if (result?.confirmed) {
console.log('User confirmed!');
}
},
children: (
{
result = { confirmed: true };
dismissGlobalFittedSheet('confirmDialog');
}}
/>
),
});
// 3. Use a component with useState for dynamic data
const FormSheet = () => {
const [name, setName] = useState('');
return (
{
console.log('Submitted:', name);
dismissGlobalFittedSheet('formSheet');
}}
/>
);
};
presentGlobalFittedSheet({
name: 'formSheet',
children: ,
});
```
### Passing Data (FittedSheet with Ref)
For `FittedSheet` components with refs, you can pass data using the `show(data)` method and receive return values via `hide(returnValue)`:
> **Note**: This pattern only works with `FittedSheet` components that have a ref. For Global Sheets, see the "Passing Data to Global Sheets" section above.
```tsx
import { FittedSheet, type FittedSheetRef } from 'react-native-sheet';
import { useRef } from 'react';
export const DataExample = () => {
const sheetRef = useRef(null);
const openWithData = () => {
sheetRef.current?.show({ userId: 123, name: 'John' });
};
return (
<>
{
console.log('Sheet dismissed with:', returnValue);
}}
>
{(data) => (
User: {data?.name}
ID: {data?.userId}
sheetRef.current?.hide({ success: true })}
/>
)}
>
);
};
```
### Customization
```tsx
```
### Multiple Sheets
```tsx
export const MultipleExample = () => {
return (
<>
presentFittedSheet('first')}
/>
First Sheet
presentFittedSheet('second')}
/>
Second Sheet
dismissFittedSheetsAll()}
/>
>
);
};
```
## API Reference
### FittedSheet Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `name` | `string` | `undefined` | Optional identifier for imperative API control |
| `params` | `SheetParams` | `{}` | Configuration options for the sheet |
| `onSheetDismiss` | `(value?: any) => void` | `undefined` | Callback when sheet is dismissed |
| `rootViewStyle` | `ViewStyle` | `undefined` | Style for internal root view |
| `children` | `ReactNode \| ((data?: any) => ReactNode)` | - | Content to render in sheet |
### SheetParams
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| `applyMaxHeightToMinHeight` | `boolean` | `false` | Apply max height constraint to min height |
| `dismissable` | `boolean` | `true` | Allow dismissing by tapping outside |
| `maxPortraitWidth` | `number` | `undefined` | Maximum width in portrait mode |
| `maxLandscapeWidth` | `number` | `undefined` | Maximum width in landscape mode |
| `maxHeight` | `number` | `undefined` | Maximum height of sheet |
| `minHeight` | `number` | `undefined` | Minimum height of sheet |
| `topLeftRightCornerRadius` | `number` | `20` | Radius for top corners |
| `backgroundColor` | `string` | `'white'` | Background color of sheet |
| `isSystemUILight` | `boolean` | `undefined` | Android only - status bar styling |
### FittedSheetRef Methods
```typescript
interface FittedSheetRef {
show(data?: any): void; // Show sheet with optional data
hide(passThroughParam?: any): void; // Hide sheet with optional callback param
attachScrollViewToSheet(): void; // Attach scrollview for better scrolling
}
```
### Imperative API
```typescript
// Show a named sheet
presentFittedSheet(name: string, data?: any): void
// Hide a specific named sheet
dismissFittedSheet(name: string, passThroughParam?: any): void
// Hide all sheets
dismissFittedSheetsAll(): void
// Hide the most recently presented sheet
dismissFittedPresented(): void
// Attach scrollview to a named sheet
attachScrollViewToFittedSheet(name: string): void
// Global Sheet API - Show a sheet without pre-declaring a component
presentGlobalFittedSheet(params: {
name: string;
onDismiss?: () => void;
sheetProps?: SheetProps;
children: ReactElement | ReactElement[];
}): void
// Dismiss a specific global sheet by name
dismissGlobalFittedSheet(name: string): void
// Attach scrollview to a global sheet by name (for dynamic ScrollView content)
attachScrollViewToGlobalFittedSheet(name: string): boolean
```
### SheetProvider Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | - | Your app content |
| `addGlobalSheetView` | `boolean` | `false` | Enable global sheet API |
| `globalSheetProps` | `Omit` | `undefined` | Default props for global sheets |
## Advanced Examples
### Dynamic Content Height
The sheet automatically resizes when content height changes:
```tsx
export const DynamicExample = () => {
const [items, setItems] = useState([1, 2, 3]);
const sheetRef = useRef(null);
return (
{items.map(item => (
Item {item}
))}
setItems([...items, items.length + 1])}
/>
);
};
```
### Preventing Dismissal
```tsx
This sheet can't be dismissed by tapping outside
sheetRef.current?.hide()}
/>
```
### ScrollView Integration
When using scrollable content, avoid wrapping in an additional `View`. Use a fragment instead for better performance:
```tsx
import { ScrollView } from 'react-native';
export const ScrollExample = () => {
const sheetRef = useRef(null);
useEffect(() => {
// Attach scrollview for better scroll handling
sheetRef.current?.attachScrollViewToSheet();
}, []);
return (
<>
{/* Your scrollable content */}
>
);
};
```
**Important:** When using `ScrollView`, `FlatList`, or other scrollable components, do not wrap them in an additional `View`:
```tsx
// ✅ Good - use fragment
<>
...
>
// ❌ Bad - avoid extra View wrapper
...
// ⚠️ If you must wrap ScrollView in a View, add flexGrow: 1
...
```
**Note:** If you need to wrap `ScrollView` in a `View` (e.g., for additional styling), make sure to add `flexGrow: 1` to the wrapper `View` style. This ensures proper height calculation for the sheet content.
## Platform-Specific Notes
### iOS
- Uses native UIViewController presentation
- Supports safe area insets automatically
- Corner radius applied to top-left and top-right corners
### Android
- Uses Material BottomSheetDialog
- Supports status bar styling via `isSystemUILight` param
## Requirements
- React Native >= 0.70 (Fabric support)
- iOS >= 12.0
- Android minSdkVersion >= 21
## Troubleshooting
### Sheet not appearing
Make sure your app is wrapped with `SheetProvider`:
```tsx
```
### Content not resizing
Ensure your content has proper height constraints or use `flexGrow` instead of `flex: 1`.
### Global sheet not working
Make sure you've enabled global sheets in `SheetProvider`:
```tsx
```
### ScrollView not working in global sheet
If you're loading ScrollView content asynchronously, you need to attach it after the content renders:
```tsx
attachScrollViewToGlobalFittedSheet('sheetName');
```
This should be called after your ScrollView component has mounted (typically in a `useEffect` hook after data loading completes).
### How to pass data to global sheets?
Global sheets don't support the `show(data)` / `hide(returnValue)` pattern like FittedSheet with refs. Instead, use closures, variables, or component state:
- **Pass data in**: Use closure variables or props in the children component
- **Return data**: Use a closure variable and set it before calling `dismissGlobalFittedSheet`, then handle it in `onDismiss` callback
- **Dynamic data**: Use a component with `useState` as children
See the "Passing Data to Global Sheets" section in the documentation for examples.
## Example App
To run the example app:
```sh
# Install dependencies
yarn
# Run on iOS
yarn example ios
# Run on Android
yarn example android
```
The example app includes demonstrations of:
- Basic usage
- Named sheets
- Global sheet API
- Data passing
- Multiple sheets
- Dynamic content
- ScrollView integration
- Queue management
- Custom styling
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for details on:
- Development workflow
- Sending pull requests
- Code of conduct
## License
MIT
---
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)