BottomSheet
A smooth, customizable modal that slides up from the bottom
A beautiful modal component that slides up from the bottom of the screen with smooth animations and intuitive gestures. Perfect for displaying additional options, menus, filters, and forms without taking up the full screen.
Overview
BottomSheet handles animations, touch gestures, and swipe-to-close interactions automatically. It provides a native feel with customizable content and styling.
Use it for:
- Action menus and options
- Filter and sorting panels
- Secondary forms and input
- Share sheets
- Detailed information panels
- Bottom navigation menus
Basic Usage
import { BottomSheet, Button } from 'prizmux'
import { useState } from 'react'
import { Text, View } from 'react-native'
export default function App() {
const [visible, setVisible] = useState(false)
return (
<>
<Button
title="Open Sheet"
onPress={() => setVisible(true)}
/>
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
title="Options"
>
<Text>Sheet content goes here</Text>
</BottomSheet>
</>
)
}
import { BottomSheet, Button } from 'prizmux'
import { useState } from 'react'
import { Text, View } from 'react-native'
export default function App() {
const [visible, setVisible] = useState(false)
return (
<>
<Button
title="Open Sheet"
onPress={() => setVisible(true)}
/>
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
title="Options"
>
<Text>Sheet content goes here</Text>
</BottomSheet>
</>
)
}
With Custom Content
import { BottomSheet, Button } from 'prizmux'
import { View, Text } from 'react-native'
import { useState } from 'react'
function ActionSheet() {
const [visible, setVisible] = useState(false)
const actions = [
{ label: 'Edit', icon: '✏️', onPress: () => {} },
{ label: 'Share', icon: '📤', onPress: () => {} },
{ label: 'Delete', icon: '🗑️', onPress: () => {} },
]
return (
<>
<Button
title="Menu"
onPress={() => setVisible(true)}
/>
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
title="Actions"
showDragHandle={true}
swipeToClose={true}
>
<View style={{ paddingHorizontal: 16, paddingBottom: 20 }}>
{actions.map((action, idx) => (
<Button
key={idx}
title={action.label}
icon={<Text>{action.icon}</Text>}
iconPosition="left"
fullWidth={true}
variant="outline"
style={{ marginBottom: 12 }}
onPress={() => {
action.onPress()
setVisible(false)
}}
/>
))}
</View>
</BottomSheet>
</>
)
}
import { BottomSheet, Button } from 'prizmux'
import { View, Text } from 'react-native'
import { useState } from 'react'
function ActionSheet() {
const [visible, setVisible] = useState(false)
const actions = [
{ label: 'Edit', icon: '✏️', onPress: () => {} },
{ label: 'Share', icon: '📤', onPress: () => {} },
{ label: 'Delete', icon: '🗑️', onPress: () => {} },
]
return (
<>
<Button
title="Menu"
onPress={() => setVisible(true)}
/>
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
title="Actions"
showDragHandle={true}
swipeToClose={true}
>
<View style={{ paddingHorizontal: 16, paddingBottom: 20 }}>
{actions.map((action, idx) => (
<Button
key={idx}
title={action.label}
icon={<Text>{action.icon}</Text>}
iconPosition="left"
fullWidth={true}
variant="outline"
style={{ marginBottom: 12 }}
onPress={() => {
action.onPress()
setVisible(false)
}}
/>
))}
</View>
</BottomSheet>
</>
)
}
With Dismiss on Outside Tap
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
dismissOnTouchOutside={true}
showCloseButton={true}
>
<Text>Tap outside to close</Text>
</BottomSheet>
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
dismissOnTouchOutside={true}
showCloseButton={true}
>
<Text>Tap outside to close</Text>
</BottomSheet>
Without Drag Handle
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
showDragHandle={false}
showCloseButton={true}
>
<Text>Close using the button</Text>
</BottomSheet>
<BottomSheet
visible={visible}
onClose={() => setVisible(false)}
showDragHandle={false}
showCloseButton={true}
>
<Text>Close using the button</Text>
</BottomSheet>
Props Reference
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | false | Control sheet visibility |
onClose | () => void | Required | Called when sheet is dismissed |
title | string | - | Optional header title |
children | ReactNode | Required | Sheet content |
dismissOnTouchOutside | boolean | true | Close when tapping backdrop |
showCloseButton | boolean | true | Display close button |
showDragHandle | boolean | true | Show drag handle indicator |
swipeToClose | boolean | true | Enable swipe-to-close gesture |
closeIcon | ReactNode | - | Custom close button icon |
Usage
Basic Bottom Sheet
import { BottomSheet, Button, Text } from 'prizmux'
import { useState } from 'react'
export default function App() {
const [visible, setVisible] = useState(false)
return (
<>
<Button
title="Open Sheet"
onPress={() => setVisible(true)}
/>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[200]}
>
<Text>Sheet content</Text>
</BottomSheet>
</>
)
}
import { BottomSheet, Button, Text } from 'prizmux'
import { useState } from 'react'
export default function App() {
const [visible, setVisible] = useState(false)
return (
<>
<Button
title="Open Sheet"
onPress={() => setVisible(true)}
/>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[200]}
>
<Text>Sheet content</Text>
</BottomSheet>
</>
)
}
With Multiple Snap Points
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[100, 300, 600]}
>
<Text>Drag to snap to different positions</Text>
</BottomSheet>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[100, 300, 600]}
>
<Text>Drag to snap to different positions</Text>
</BottomSheet>
Action Menu
import { BottomSheet, Button, Text } from 'prizmux'
import { View } from 'react-native'
function ActionMenu() {
const [visible, setVisible] = useState(false)
const actions = [
{ label: 'Edit', onPress: handleEdit },
{ label: 'Share', onPress: handleShare },
{ label: 'Delete', onPress: handleDelete },
]
return (
<>
<Button
title="Options"
onPress={() => setVisible(true)}
/>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[250]}
>
<View style={{ paddingHorizontal: 16, gap: 12 }}>
{actions.map((action) => (
<Button
key={action.label}
title={action.label}
variant="outline"
onPress={() => {
action.onPress()
setVisible(false)
}}
/>
))}
</View>
</BottomSheet>
</>
)
}
import { BottomSheet, Button, Text } from 'prizmux'
import { View } from 'react-native'
function ActionMenu() {
const [visible, setVisible] = useState(false)
const actions = [
{ label: 'Edit', onPress: handleEdit },
{ label: 'Share', onPress: handleShare },
{ label: 'Delete', onPress: handleDelete },
]
return (
<>
<Button
title="Options"
onPress={() => setVisible(true)}
/>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[250]}
>
<View style={{ paddingHorizontal: 16, gap: 12 }}>
{actions.map((action) => (
<Button
key={action.label}
title={action.label}
variant="outline"
onPress={() => {
action.onPress()
setVisible(false)
}}
/>
))}
</View>
</BottomSheet>
</>
)
}
Filter Sheet
import { BottomSheet, Button, Text, Checkbox } from 'prizmux'
import { View } from 'react-native'
function FilterSheet() {
const [visible, setVisible] = useState(false)
const [filters, setFilters] = useState({
inStock: true,
onSale: false,
})
return (
<>
<Button
title="Filter"
onPress={() => setVisible(true)}
/>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[300]}
>
<View style={{ padding: 16, gap: 16 }}>
<Text variant="title">Filters</Text>
<Checkbox
label="In Stock"
value={filters.inStock}
onChange={(inStock) =>
setFilters({ ...filters, inStock })
}
/>
<Checkbox
label="On Sale"
value={filters.onSale}
onChange={(onSale) =>
setFilters({ ...filters, onSale })
}
/>
<Button
title="Apply Filters"
onPress={() => setVisible(false)}
/>
</View>
</BottomSheet>
</>
)
}
import { BottomSheet, Button, Text, Checkbox } from 'prizmux'
import { View } from 'react-native'
function FilterSheet() {
const [visible, setVisible] = useState(false)
const [filters, setFilters] = useState({
inStock: true,
onSale: false,
})
return (
<>
<Button
title="Filter"
onPress={() => setVisible(true)}
/>
<BottomSheet
isVisible={visible}
onClose={() => setVisible(false)}
snapPoints={[300]}
>
<View style={{ padding: 16, gap: 16 }}>
<Text variant="title">Filters</Text>
<Checkbox
label="In Stock"
value={filters.inStock}
onChange={(inStock) =>
setFilters({ ...filters, inStock })
}
/>
<Checkbox
label="On Sale"
value={filters.onSale}
onChange={(onSale) =>
setFilters({ ...filters, onSale })
}
/>
<Button
title="Apply Filters"
onPress={() => setVisible(false)}
/>
</View>
</BottomSheet>
</>
)
}
Notes
- BottomSheet automatically handles backdrop dismiss
- Supports gestures for dismissing and resizing
- Content is automatically scrollable if it exceeds snap points
- Handle indicator helps users understand they can drag
- Works seamlessly with keyboard