How to Create a Multi-Pane Drawer in React Native
Does your app’s menu drawer have too much going on? Splitting it up into separate panes might be the solution. An example you’re probably familiar with is Slack’s mobile app, which lists channels and direct messages on the main pane, and a second pane for switching between workspaces. (Speaking of Slack, check out Jamon Holmgren’s post on his favorite Slack hacks!)

This is a simple guide, but I am assuming you understand the basics of React Navigation. If you don’t, then check out the official guide and then come back. All of the code is available on GitHub here. Let’s get started!
Add DrawerNavigator
Create your RootNavigator using createDrawerNavigator, and set contentComponent
to a Drawer component that we will create next.
// app/navigation/root-navigator.ts
import { createDrawerNavigator } from "react-navigation"
import { Drawer } from "./drawer/drawer"export const RootNavigator = createDrawerNavigator(
{
exampleScreen: { screen: FirstExampleScreen },
},
{
initialRouteName: "exampleScreen",
contentComponent: Drawer,
},
)
The Drawer component is just a SafeAreaView for now.
// app/navigation/drawer/drawer.tsx
import * as React from "react"
import { SafeAreaView } from "react-native"
import { NavigationInjectedProps } from "react-navigation"interface DrawerState {}export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> {
render() {
return <SafeAreaView />
}
}
Make sure the drawer is working before we move on.

Create Panes
Now let’s add two panes to the drawer. These panes could contain whatever you want; one for chat channels and another for workspaces, for example. We’ll leave the content for you to decide, and just put a label in each one.
// app/navigation/drawer/drawer.tsx
import * as React from "react"
import { Text, Animated, SafeAreaView } from "react-native"
import { NavigationInjectedProps } from "react-navigation"interface DrawerState {
drawerWidth?: number
}export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> {
state = {
drawerWidth: 0,
}
render() {
const { drawerWidth } = this.state
return (
<SafeAreaView
onLayout={event => {
this.setState({
drawerWidth: event.nativeEvent.layout.width,
})
}}
style={{
flexDirection: "row",
height: "100%",
}}
>
{/* Left Pane */}
<Animated.View
style={{
left: -drawerWidth,
width: drawerWidth,
backgroundColor: "#00c3e3",
}}
>
<Text>Left Pane</Text>
</Animated.View> {/* Right Pane */}
<Animated.View
style={{
left: -drawerWidth,
width: drawerWidth,
backgroundColor: "#ff4554",
}}
>
<Text>Right Pane</Text>
</Animated.View>
</SafeAreaView>
)
}
}
onLayout
is a callback called whenever the React Native runtime does the layout for a component. This callback receives an event with properties including the one we’re interested in, the width of the outermost SafeAreaView of the drawer. We need to know this so we can size the panes exactly the same width as the drawer. That way, when a pane is displayed it takes up the whole drawer; nothing more, nothing less. In onLayout
, we will receive the width and save it in the drawer’s state as drawerWidth
. Be sure to initialize drawerWidth
so that it’s not undefined before the first layout happens.
Let’s have a look at our drawer.

Only the right pane is visible. The other pane is naturally to the left of this, off screen. That’s because we set the left
property of each pane to minus the width of the drawer, which moves the left pane off screen and the right pane perfectly into the drawer.
Switch Panes
To switch between panes, we just need to change the left
property of each pane back to zero when a button is pressed. Add a boolean property to DrawerState
that will determine whether to switch to the left drawer; initialize this value to false
so the drawer starts off showing the right pane. Next create a method to render the button; this will be used in both panes.
interface DrawerState {
drawerWidth?: number
displayLeftPane?: boolean
}export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> {
state = {
drawerWidth: 0,
displayLeftPane: false,
}
// …renderSlideButton() {
return (
<TouchableOpacity
style={{
height: 100,
marginVertical: 50,
}}
onPress={() => {
this.setState({
displayLeftPane: !this.state.displayLeftPane,
})
}}
>
<Text>Slide Drawer</Text>
</TouchableOpacity>
)
}
}
Now inside render
make the following changes. Extract displayLeftPane
from the state and use it to conditionally set paneShift
. Then change left
in each pane’s style to paneShift
. Also add overflow: “hidden"
to the SafeAreaView
style so that the right pane doesn’t remain visible as it slides out of the drawer region. Finally, add the method renderSlideButton
that we just wrote to each button’s contents. You can see the whole file in the repo here.
render() {
const { drawerWidth, displayLeftPane } = this.state
const paneShift = displayLeftPane ? 0 : -drawerWidth // … return (
<SafeAreaView
onLayout={…}
style={{
flexDirection: "row",
height: "100%",
overflow: "hidden",
}}
>
{/* Left Pane */}
<Animated.View
style={{
left: paneShift,
width: drawerWidth,
backgroundColor: "#00c3e3",
}}
>
<Text>Left Pane</Text>
{this.renderSlideButton()}
</Animated.View> {/* Right Pane */}
<Animated.View
style={{
left: paneShift,
width: drawerWidth,
backgroundColor: "#ff4554",
}}
>
<Text>Right Pane</Text>
{this.renderSlideButton()}
</Animated.View>
</SafeAreaView>
)
}
Let’s see it!

Animate
That’s great, but let’s have the panes slide instead of abruptly jump. This can be done with a few simple changes.
First, add a new Animated.Value
property to DrawerState
and initialize it like so:
interface DrawerState {
drawerWidth?: number
displayLeftPane?: boolean
slideProgress?: Animated.Value
}export class Drawer extends React.Component<NavigationInjectedProps, DrawerState> {
state = {
drawerWidth: 0,
displayLeftPane: false,
slideProgress: new Animated.Value(0),
}
This value will represent the progress of the sliding process, having value 0 when the drawer is in the starting position, and value 1 when it has completely slid left, and intermediate values while it is sliding.
Next, animate this property to its correct value inside componentDidMount
.
componentDidUpdate() {
const { displayLeftPane, slideProgress } = this.state
const toValue = displayLeftPane ? 1 : 0
Animated.timing(slideProgress, {
toValue,
duration: 250,
}).start()
}
Finally, use slideProgress
to calculate slidePosition
:
const paneShift = this.state.slideProgress.interpolate({
inputRange: [0, 1],
outputRange: [-drawerWidth, 0],
})
That’s it! The drawer will now slide nicely back and forth.

Can I Use Gestures to Switch Between Panes?
Up to this point, I haven’t been able to do it using React Native’s built-in drawer component. React Native’s drawer comes from react-navigation-drawer, which uses react-native-gesture-handler’s GestureHandler. GestureHandler is set up to intercept the touch events that would allow you to switch the pane with swiping gestures. (Through experimentation, I found out that it actually works if you start your gesture off the drawer, but that’s neither obvious nor good UX). If you find a way, please do share.
Thanks for checking out this guide, please let me know what you think in the comments or here on Twitter!