Drawer
A sliding panel that enters from the edge of the screen, commonly used for navigation or filters in mobile applications.
Installation
Install the component:
npx natively-ui add drawer
Usage
DrawerDemo.jsx
1import { Drawer } from '@/components/ui/drawer';
2
3export default function MyComponent() {
4 const [isOpen, setIsOpen] = React.useState(false);
5
6 return (
7 <>
8 <Button onPress={() => setIsOpen(true)}>
9 Open Drawer
10 </Button>
11
12 <Drawer
13 isOpen={isOpen}
14 onClose={() => setIsOpen(false)}
15 side="left"
16 >
17 <View style={{ padding: 16 }}>
18 <Text style={{ fontSize: 20, fontWeight: 'bold' }}>
19 Navigation
20 </Text>
21 <View style={{ marginTop: 16 }}>
22 <Text>Menu Item 1</Text>
23 <Text>Menu Item 2</Text>
24 <Text>Menu Item 3</Text>
25 </View>
26 </View>
27 </Drawer>
28 </>
29 );
30}
Props
Prop | Type | Default | Description |
---|---|---|---|
isOpen | boolean | false | Controls the visibility of the drawer. |
onClose | () => void | - | Callback when the drawer is closed. |
side | 'left' | 'right' | 'top' | 'bottom' | 'left' | The side from which the drawer appears. |
width | number | 300 | Width of the drawer (for left/right drawers). |
height | number | 300 | Height of the drawer (for top/bottom drawers). |
overlayColor | string | 'rgba(0, 0, 0, 0.5)' | Color of the overlay behind the drawer. |
overlayClose | boolean | true | Whether clicking the overlay closes the drawer. |
closeOnEscape | boolean | true | Whether pressing escape closes the drawer. |
children | React.ReactNode | - | The content to display inside the drawer. |
className | string | "" | Additional CSS classes for the drawer container. |
enableGestures | boolean | false | Enable swipe gestures to close the drawer. |
Features
- Smooth slide animations with React Native Reanimated
- Multiple placement options (left, right, top, bottom)
- Customizable dimensions and overlay styling
- Backdrop overlay with tap-to-close functionality
- Gesture-based interactions for intuitive closing
- Keyboard navigation support with escape key
- Accessible focus management and screen reader support
- Safe area insets handling for notched devices
- Portal rendering for proper z-index stacking
- Customizable animation duration and easing
Examples
Navigation Drawer
NavigationDrawer.jsx
1function NavigationDrawer() {
2 const [isOpen, setIsOpen] = useState(false);
3
4 const navigationItems = [
5 { title: 'Home', icon: 'home' },
6 { title: 'Profile', icon: 'user' },
7 { title: 'Settings', icon: 'settings' },
8 { title: 'Help', icon: 'help-circle' },
9 ];
10
11 return (
12 <>
13 <Button
14 leftIcon={<Icon name="menu" size={16} />}
15 onPress={() => setIsOpen(true)}
16 >
17 Menu
18 </Button>
19
20 <Drawer
21 isOpen={isOpen}
22 onClose={() => setIsOpen(false)}
23 side="left"
24 width={280}
25 >
26 <View style={{ padding: 20 }}>
27 <Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 20 }}>
28 Navigation
29 </Text>
30
31 {navigationItems.map((item, index) => (
32 <Pressable
33 key={index}
34 style={{
35 flexDirection: 'row',
36 alignItems: 'center',
37 paddingVertical: 12,
38 paddingHorizontal: 16,
39 borderRadius: 8,
40 }}
41 onPress={() => {
42 console.log(`Navigate to ${item.title}`);
43 setIsOpen(false);
44 }}
45 >
46 <Icon name={item.icon} size={20} style={{ marginRight: 12 }} />
47 <Text style={{ fontSize: 16 }}>{item.title}</Text>
48 </Pressable>
49 ))}
50 </View>
51 </Drawer>
52 </>
53 );
54}
Bottom Sheet
BottomSheet.jsx
1function BottomSheet() {
2 const [isOpen, setIsOpen] = useState(false);
3
4 return (
5 <>
6 <Button onPress={() => setIsOpen(true)}>
7 Show Options
8 </Button>
9
10 <Drawer
11 isOpen={isOpen}
12 onClose={() => setIsOpen(false)}
13 side="bottom"
14 height={400}
15 overlayColor="rgba(0, 0, 0, 0.7)"
16 enableGestures={true}
17 >
18 <View style={{ padding: 20 }}>
19 <View style={{
20 width: 40,
21 height: 4,
22 backgroundColor: '#ccc',
23 borderRadius: 2,
24 alignSelf: 'center',
25 marginBottom: 20
26 }} />
27
28 <Text style={{ fontSize: 20, fontWeight: 'bold', marginBottom: 16 }}>
29 Quick Actions
30 </Text>
31
32 <View style={{ gap: 12 }}>
33 <Button variant="outline">Share</Button>
34 <Button variant="outline">Edit</Button>
35 <Button variant="outline">Duplicate</Button>
36 <Button variant="destructive">Delete</Button>
37 </View>
38 </View>
39 </Drawer>
40 </>
41 );
42}
Filter Drawer
FilterDrawer.jsx
1function FilterDrawer() {
2 const [isOpen, setIsOpen] = useState(false);
3 const [filters, setFilters] = useState({
4 category: 'all',
5 priceRange: [0, 1000],
6 inStock: false
7 });
8
9 return (
10 <>
11 <Button
12 rightIcon={<Icon name="filter" size={16} />}
13 onPress={() => setIsOpen(true)}
14 >
15 Filters
16 </Button>
17
18 <Drawer
19 isOpen={isOpen}
20 onClose={() => setIsOpen(false)}
21 side="right"
22 width={320}
23 overlayClose={false}
24 >
25 <View style={{ padding: 20, flex: 1 }}>
26 <View style={{
27 flexDirection: 'row',
28 justifyContent: 'space-between',
29 alignItems: 'center',
30 marginBottom: 24
31 }}>
32 <Text style={{ fontSize: 20, fontWeight: 'bold' }}>
33 Filters
34 </Text>
35 <Button
36 size="icon"
37 variant="ghost"
38 onPress={() => setIsOpen(false)}
39 >
40 <Icon name="x" size={20} />
41 </Button>
42 </View>
43
44 <View style={{ gap: 24, flex: 1 }}>
45 <View>
46 <Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 8 }}>
47 Category
48 </Text>
49 {/* Category selection components */}
50 </View>
51
52 <View>
53 <Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 8 }}>
54 Price Range
55 </Text>
56 {/* Price range slider component */}
57 </View>
58
59 <View>
60 <Text style={{ fontSize: 16, fontWeight: '600', marginBottom: 8 }}>
61 Availability
62 </Text>
63 {/* Checkbox component */}
64 </View>
65 </View>
66
67 <View style={{ flexDirection: 'row', gap: 12, marginTop: 'auto' }}>
68 <Button
69 variant="outline"
70 style={{ flex: 1 }}
71 onPress={() => setFilters({ category: 'all', priceRange: [0, 1000], inStock: false })}
72 >
73 Reset
74 </Button>
75 <Button
76 style={{ flex: 1 }}
77 onPress={() => {
78 console.log('Apply filters:', filters);
79 setIsOpen(false);
80 }}
81 >
82 Apply
83 </Button>
84 </View>
85 </View>
86 </Drawer>
87 </>
88 );
89}
Multiple Directions
MultiDirectionDrawers.jsx
1function MultiDirectionDrawers() {
2 const [activeDrawer, setActiveDrawer] = useState(null);
3
4 const drawers = [
5 { side: 'left', label: 'Left Menu', width: 280 },
6 { side: 'right', label: 'Right Panel', width: 320 },
7 { side: 'top', label: 'Top Banner', height: 200 },
8 { side: 'bottom', label: 'Bottom Sheet', height: 300 }
9 ];
10
11 return (
12 <View style={{ gap: 12, padding: 20 }}>
13 {drawers.map((drawer) => (
14 <Button
15 key={drawer.side}
16 variant="outline"
17 onPress={() => setActiveDrawer(drawer.side)}
18 >
19 Open {drawer.label}
20 </Button>
21 ))}
22
23 {drawers.map((drawer) => (
24 <Drawer
25 key={drawer.side}
26 isOpen={activeDrawer === drawer.side}
27 onClose={() => setActiveDrawer(null)}
28 side={drawer.side}
29 width={drawer.width}
30 height={drawer.height}
31 >
32 <View style={{
33 padding: 20,
34 alignItems: 'center',
35 justifyContent: 'center',
36 flex: 1
37 }}>
38 <Text style={{ fontSize: 18, fontWeight: 'bold' }}>
39 {drawer.label}
40 </Text>
41 <Text style={{ marginTop: 8, textAlign: 'center' }}>
42 This drawer slides in from the {drawer.side}
43 </Text>
44 <Button
45 style={{ marginTop: 16 }}
46 onPress={() => setActiveDrawer(null)}
47 >
48 Close
49 </Button>
50 </View>
51 </Drawer>
52 ))}
53 </View>
54 );
55}
Nested Drawers
NestedDrawers.jsx
1function NestedDrawers() {
2 const [primaryOpen, setPrimaryOpen] = useState(false);
3 const [secondaryOpen, setSecondaryOpen] = useState(false);
4
5 return (
6 <>
7 <Button onPress={() => setPrimaryOpen(true)}>
8 Open Main Menu
9 </Button>
10
11 <Drawer
12 isOpen={primaryOpen}
13 onClose={() => setPrimaryOpen(false)}
14 side="left"
15 width={280}
16 >
17 <View style={{ padding: 20 }}>
18 <Text style={{ fontSize: 20, fontWeight: 'bold', marginBottom: 20 }}>
19 Main Menu
20 </Text>
21
22 <Button
23 variant="outline"
24 style={{ marginBottom: 12 }}
25 onPress={() => setSecondaryOpen(true)}
26 >
27 Open Sub Menu
28 </Button>
29
30 <Button variant="outline">
31 Other Option
32 </Button>
33 </View>
34 </Drawer>
35
36 <Drawer
37 isOpen={secondaryOpen}
38 onClose={() => setSecondaryOpen(false)}
39 side="right"
40 width={250}
41 overlayColor="rgba(0, 0, 0, 0.3)"
42 >
43 <View style={{ padding: 20 }}>
44 <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 16 }}>
45 Sub Menu
46 </Text>
47
48 <Text>Nested drawer content</Text>
49
50 <Button
51 style={{ marginTop: 20 }}
52 onPress={() => setSecondaryOpen(false)}
53 >
54 Close Sub Menu
55 </Button>
56 </View>
57 </Drawer>
58 </>
59 );
60}