Drawer

The Drawer component provides a slide-in panel for navigation, forms, or supplementary content, appearing from the edge of the screen.

Installation

$ cbui-cli install @crossbuildui/drawer

Import

MyComponent.tsx
1import { Drawer } from '@/crossbuildui/drawer'; 2import type { DrawerPlacement, DrawerSize, DrawerRadius, DrawerBackdrop, DrawerAnimationConfig } from '@crossbuildui/drawer'; // Optional: for type safety 3import { Button } from '@/crossbuildui/button'; // Or your preferred button component

Drawer Component

Basic Usage

Control the Drawer's visibility using the isOpen and onOpenChange props. Provide content as children.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 4 return ( 5 <View> 6 <Button onPress={() => setIsOpen(true)}>Open Drawer</Button> 7 <Drawer 8 isOpen={isOpen} 9 onOpenChange={setIsOpen} 10 placement="left" 11 > 12 <View style={{ padding: 20, flex: 1 }}> 13 <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>Drawer Content</Text> 14 <Text>This is the content of the drawer.</Text> 15 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 20 }}> 16 Close Drawer 17 </Button> 18 </View> 19 </Drawer> 20 </View> 21 ); 22}

Placement

The placement prop determines which edge the drawer slides in from: left (default), right, top, or bottom.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 const [placement, setPlacement] = useState<DrawerPlacement>('left'); 4 5 const openDrawer = (p: DrawerPlacement) => { 6 setPlacement(p); 7 setIsOpen(true); 8 }; 9 10 return ( 11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}> 12 <Button onPress={() => openDrawer('left')}>Open Left</Button> 13 <Button onPress={() => openDrawer('right')}>Open Right</Button> 14 <Button onPress={() => openDrawer('top')}>Open Top</Button> 15 <Button onPress={() => openDrawer('bottom')}>Open Bottom</Button> 16 17 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} placement={placement}> 18 <View style={{ padding: 20, flex: 1, alignItems: 'center', justifyContent: 'center' }}> 19 <Text>Drawer from {placement}</Text> 20 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button> 21 </View> 22 </Drawer> 23 </View> 24 ); 25}

Sizes

Adjust the drawer's size (width for left/right, height for top/bottom) using the size prop. Options: sm, md (default), lg, full.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 const [size, setSize] = useState<DrawerSize>('md'); 4 5 const openDrawer = (s: DrawerSize) => { 6 setSize(s); 7 setIsOpen(true); 8 }; 9 10 return ( 11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}> 12 <Button onPress={() => openDrawer('sm')}>Small</Button> 13 <Button onPress={() => openDrawer('md')}>Medium</Button> 14 <Button onPress={() => openDrawer('lg')}>Large</Button> 15 <Button onPress={() => openDrawer('full')}>Full</Button> 16 17 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} size={size} placement="left"> 18 <View style={{ padding: 20, flex: 1 }}> 19 <Text>Drawer size: {size}</Text> 20 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button> 21 </View> 22 </Drawer> 23 </View> 24 ); 25}

Radius

Apply a border radius to the relevant corners of the drawer panel with the radius prop. Options: none, sm, md, lg (default).

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 const [radius, setRadius] = useState<DrawerRadius>('lg'); 4 5 const openDrawer = (r: DrawerRadius) => { 6 setRadius(r); 7 setIsOpen(true); 8 }; 9 10 return ( 11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}> 12 <Button onPress={() => openDrawer('none')}>None</Button> 13 <Button onPress={() => openDrawer('sm')}>Small</Button> 14 <Button onPress={() => openDrawer('md')}>Medium</Button> 15 <Button onPress={() => openDrawer('lg')}>Large</Button> 16 17 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} radius={radius} placement="right"> 18 <View style={{ padding: 20, flex: 1 }}> 19 <Text>Drawer radius: {radius}</Text> 20 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button> 21 </View> 22 </Drawer> 23 </View> 24 ); 25}

Backdrop

Customize the backdrop using the backdrop prop: opaque (default, semi-transparent black), blur (requires expo-blur), or transparent.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 const [backdrop, setBackdrop] = useState<DrawerBackdrop>('opaque'); 4 5 const openDrawer = (b: DrawerBackdrop) => { 6 setBackdrop(b); 7 setIsOpen(true); 8 }; 9 10 return ( 11 <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}> 12 <Button onPress={() => openDrawer('opaque')}>Opaque</Button> 13 <Button onPress={() => openDrawer('blur')}>Blur</Button> 14 <Button onPress={() => openDrawer('transparent')}>Transparent</Button> 15 16 <Drawer isOpen={isOpen} onOpenChange={setIsOpen} backdrop={backdrop}> 17 <View style={{ padding: 20, flex: 1 }}> 18 <Text>Backdrop: {backdrop}</Text> 19 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 10 }}>Close</Button> 20 </View> 21 </Drawer> 22 </View> 23 ); 24}

Dismissable Behavior

By default (isDismissable=), the drawer can be closed by pressing the backdrop or the hardware back button (Android). Set to false to prevent this.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 4 return ( 5 <View> 6 <Button onPress={() => setIsOpen(true)}>Open Non-Dismissable Drawer</Button> 7 <Drawer 8 isOpen={isOpen} 9 onOpenChange={setIsOpen} 10 isDismissable={false} 11 > 12 <View style={{ padding: 20, flex: 1 }}> 13 <Text>This drawer cannot be dismissed by backdrop press or back button.</Text> 14 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 20 }}> 15 Close Manually 16 </Button> 17 </View> 18 </Drawer> 19 </View> 20 ); 21}

Animation Configuration

Customize the opening and closing animation using animationConfig. Supports type: 'spring' (default) or type: 'timing', with respective Reanimated configuration objects.

MyComponent.tsx
1function App() { 2 const [isOpenSpring, setIsOpenSpring] = useState(false); 3 const [isOpenTiming, setIsOpenTiming] = useState(false); 4 5 const springConfig: DrawerAnimationConfig = { 6 type: 'spring', 7 springConfig: { damping: 15, stiffness: 120 } 8 }; 9 10 const timingConfig: DrawerAnimationConfig = { 11 type: 'timing', 12 timingConfig: { duration: 500 } 13 }; 14 15 return ( 16 <View style={{ flexDirection: 'row', gap: 8 }}> 17 <Button onPress={() => setIsOpenSpring(true)}>Open with Spring</Button> 18 <Button onPress={() => setIsOpenTiming(true)}>Open with Timing</Button> 19 20 <Drawer isOpen={isOpenSpring} onOpenChange={setIsOpenSpring} animationConfig={springConfig}> 21 <View style={{ padding: 20, flex: 1 }}><Text>Spring Animation</Text><Button onPress={() => setIsOpenSpring(false)}>Close</Button></View> 22 </Drawer> 23 24 <Drawer isOpen={isOpenTiming} onOpenChange={setIsOpenTiming} animationConfig={timingConfig} placement="right"> 25 <View style={{ padding: 20, flex: 1 }}><Text>Timing Animation</Text><Button onPress={() => setIsOpenTiming(false)}>Close</Button></View> 26 </Drawer> 27 </View> 28 ); 29}

Custom Close Button

The closeButton prop accepts a ReactNode for a custom close button. However, you are responsible for rendering this button within the drawer's children content at your desired location. The Drawer component itself does not automatically place it.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 4 const MyCustomCloseButton = ( 5 <Button 6 variant="ghost" 7 size="sm" 8 isIconOnly 9 onPress={() => setIsOpen(false)} 10 style={{ position: 'absolute', top: 10, right: 10, zIndex: 1 }} 11 > 12 <Text>X</Text> {/* Replace with an Icon component if available */} 13 </Button> 14 ); 15 16 return ( 17 <View> 18 <Button onPress={() => setIsOpen(true)}>Open Drawer with Custom Close</Button> 19 <Drawer 20 isOpen={isOpen} 21 onOpenChange={setIsOpen} 22 closeButton={MyCustomCloseButton} // Prop provided, but user places it 23 > 24 <View style={{ padding: 20, flex: 1 }}> 25 {/* User needs to render the closeButton where they want it */} 26 {MyCustomCloseButton} 27 <Text style={{ fontSize: 18, fontWeight: 'bold', marginTop: 40 }}>Drawer Title</Text> 28 <Text>Content goes here...</Text> 29 </View> 30 </Drawer> 31 </View> 32 ); 33}

Props Overview

PROPTYPEDEFAULTDESCRIPTION
childrenReactNode-Content of the drawer panel.
sizeDrawerSize'md'Size of the drawer.
radiusDrawerRadius'lg'Border radius of the panel.
placementDrawerPlacement'left'Edge from which drawer slides.
isOpenboolean-Controlled open state.
defaultOpenbooleanfalseInitial open state (uncontrolled).
isDismissablebooleantrueIf dismissable by backdrop/back press.
backdropDrawerBackdrop'opaque'Backdrop type: transparent, opaque, blur.
closeButtonReactNode-Custom close button element (user places it).
animationConfigDrawerAnimationConfig{ type: 'spring' }Animation settings (spring/timing).
stylesDrawerSlotsStyles-Styles for slots (backdrop, panel).
styleStyleProp<ViewStyle>-Style for the main drawer panel.
onOpenChange(isOpen: boolean) => void-Callback on open state change.
onClose() => void-Callback when drawer closes.

Styling

Customize the Drawer's appearance using the style prop for the main panel or the styles prop for more granular control over internal slots.

The styles prop accepts an object with the following keys:

  • backdrop: Styles applied to the backdrop overlay View or BlurView.
  • panel: Styles applied to the main drawer panel Animated.View. These are merged with and can be overridden by the main style prop.
MyComponent.tsx
1<Drawer 2 isOpen={true} // Keep open for example 3 onOpenChange={() => {}} 4 placement="left" 5 size="md" 6 style={{ borderWidth: 2, borderColor: 'purple' }} // Styles the main panel 7 styles={{ 8 backdrop: { backgroundColor: 'rgba(0, 255, 0, 0.2)' }, // Custom backdrop style 9 panel: { shadowColor: 'blue', shadowOpacity: 0.8, elevation: 10 } // Additional panel styles 10 }} 11> 12 <View style={{ padding: 20, flex: 1 }}> 13 <Text>Styled Drawer</Text> 14 </View>S 15</Drawer>

Accessibility

The Drawer component is built on top of React Native's Modal, which handles some accessibility aspects like focus management.

  • The onRequestClose prop of the underlying RNModal is connected to handleClose, aiding in hardware back button dismissal on Android.
  • Ensure any interactive elements within the drawer, including a custom close button, are properly labeled for accessibility (e.g., using accessibilityLabel).
  • The content within the drawer should be structured semantically for screen readers.
  • Consider announcing the drawer's appearance or purpose to screen reader users if it's not immediately obvious from the context.