How to make a side drawer menu in React Native
By the LumioUI team · Updated
A side drawer is a menu that slides in from the edge over a dimmed backdrop. To build one in React Native, animate the panel's translateX with Reanimated and fade the backdrop from the same value. Close it on a backdrop tap or a swipe. The LumioUI Drawer does this with rows that highlight when tapped, then close.
Live demo of the LumioUI Drawer component. It's interactive, so try it.
How a drawer menu works
- One value drives everything. The panel's offset runs from its negative width (hidden) to 0 (shown). The backdrop's opacity is derived from it, so the dim follows your finger exactly.
- Swipe to close. A horizontal pan with an activation offset, so vertical scrolling inside still works. A flick or dragging past a third closes it.
- Above everything. Render the drawer at the root, or in a portal, so it covers headers and tab bars.
- Accessibility. Mark the panel modal (
accessibilityViewIsModal) so screen readers stay inside it. Label the backdrop "Close menu", and give the active rowselectedstate.
import * as React from "react";
import { Pressable, StyleSheet, View } from "react-native";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming } from "react-native-reanimated";
const W = 300;
export function SideDrawer({ open, onClose, children }) {
const x = useSharedValue(-W); // -W hidden, 0 shown
React.useEffect(() => {
x.value = open ? withSpring(0, { damping: 24, stiffness: 240 }) : withTiming(-W, { duration: 180 });
}, [open]);
const swipe = Gesture.Pan()
.activeOffsetX([-10, 10])
.onChange((e) => { x.value = Math.min(0, x.value + e.changeX); })
.onEnd((e) => {
if (e.velocityX < -500 || x.value < -W / 3) runOnJS(onClose)();
else x.value = withSpring(0);
});
const panel = useAnimatedStyle(() => ({ transform: [{ translateX: x.value }] }));
const dim = useAnimatedStyle(() => ({ opacity: 0.4 * (1 + x.value / W) }));
return (
<View style={StyleSheet.absoluteFill} pointerEvents={open ? "auto" : "none"}>
<Animated.View style={[StyleSheet.absoluteFill, { backgroundColor: "#000" }, dim]}>
<Pressable style={{ flex: 1 }} onPress={onClose} accessibilityLabel="Close menu" />
</Animated.View>
<GestureDetector gesture={swipe}>
<Animated.View style={[{ position: "absolute", top: 0, bottom: 0, width: W, backgroundColor: "#fff" }, panel]}
accessibilityViewIsModal>
{children}
</Animated.View>
</GestureDetector>
</View>
);
} Using React Navigation? Its drawer navigator handles routing. You can still render your own drawer content inside it.
The quick way: LumioUI Drawer
The Drawer slides in over a dimmed backdrop. Its rows can appear in turn and show rolling unread counts. Tapping a row highlights it and closes the drawer, or you can swipe it away. It's free and runs in Expo Go.
import * as React from "react";
import { Drawer } from "@/components/ui/drawer/drawer";
import { Icon } from "@/components/ui/icon/icon";
export function AppMenu({ open, onOpenChange, route, go }) {
return (
<Drawer open={open} onOpenChange={onOpenChange} side="left" width={300}>
<Drawer.Header>
<Drawer.Title>Maya Chen</Drawer.Title>
</Drawer.Header>
<Drawer.Item label="Inbox" icon={<Icon name="mail" />} active={route === "inbox"} trailing="12" onPress={() => go("inbox")} />
<Drawer.Item label="Starred" icon={<Icon name="star" />} active={route === "starred"} onPress={() => go("starred")} />
<Drawer.Item label="Settings" icon={<Icon name="settings" />} active={route === "settings"} onPress={() => go("settings")} />
</Drawer>
);
} openandonOpenChangeare required (it's controlled).sideis"left"or"right", andwidthdefaults to 300.Drawer.Itemtakeslabel,icon,active,trailing(a count or a chevron) andonPress.- On tablets, prefer a Navigation Rail (Pro), which stays on screen and expands into a sidebar.
Add it to your app
LumioUI's CLI copies the component's source into your project, so it's yours to read and change. Run init once per project:
npx @lumioui/cli init
npx @lumioui/cli add drawer It works in Expo (SDK 54+) and bare React Native, with Reanimated 4 and NativeWind. See the installation guide and the full Drawer API.
Related components
- Navigation Rail (Pro)
- Side Sheet (Pro)
- App Bar
- Dock (Pro)
More React Native guides
- How to make a bottom sheet in React Native
- Animated tabs with a sliding indicator in React Native
- How to show toast notifications in React Native
- Skeleton loading screens in React Native
- How to add dark mode and a theme toggle in React Native
- How to build an accordion in React Native
- How to make an animated heart like button in React Native
- How to make a modal dialog in React Native
- How to build an animated carousel in React Native
- How to make an animated splash screen in React Native and Expo
- How to build a month calendar in React Native