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 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}

Glass Panel

Apply a frosted glass effect to the drawer panel itself using the isGlass prop. Customize its appearance with glassIntensity (0-100) and glassTint ('light', 'dark', or 'default'). When isGlass is true, the panel's direct background color becomes transparent to allow the blur effect. Ensure content within the drawer has sufficient contrast.

MyComponent.tsx
1function App() { 2 const [isOpen, setIsOpen] = useState(false); 3 4 return ( 5 <View> 6 <Button onPress={() => setIsOpen(true)}>Open Glass Drawer</Button> 7 <Drawer 8 isOpen={isOpen} 9 onOpenChange={setIsOpen} 10 placement="right" 11 isGlass 12 glassIntensity={70} 13 glassTint="light" // or 'dark', 'default' 14 > 15 <View style={{ padding: 20, flex: 1 }}> 16 <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10, color: 'black' /* Adjust for contrast */ }}> 17 Glass Panel Content 18 </Text> 19 <Button onPress={() => setIsOpen(false)} style={{ marginTop: 20 }}>Close</Button> 20 </View> 21 </Drawer> 22 </View> 23 ); 24}

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.
isGlassbooleanfalseApply glassmorphism to the drawer panel.
glassTint'default' | 'light' | 'dark'Theme-derivedTint for the panel's glass effect.
glassIntensitynumber50Intensity (0-100) for the panel's glass effect.

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.
  • If isGlass is true for the panel, a BlurView is rendered as its background. The panel styles (and main style prop) apply to the Animated.View that contains this BlurView and the children. The backgroundColor of the panel will be set to transparent to show the blur.
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.