You've got a bottom tab bar working. Then a mockup lands with a hamburger menu that slides out over the tabs: a workspace switcher, settings, a link to support. Now you need both navigation patterns at once, one nested inside the other, without the drawer swipe fighting your image carousel or the tab state resetting every time someone opens the menu.
A persistent bottom tab bar paired with a global sidebar drawer is a common pattern in cross-platform apps. Nest those navigation trees carelessly, though, and you invite performance overhead and gesture conflicts.
With Expo Router, you map these flows directly onto a file-system directory. Your code stays modular and maintainable, and the native navigation components still do the work underneath. I shared a demo of this on twitter and the Expo team reached out about sharing my process. Hopefully by the end of this post you'll have a drawer wrapping a tab navigator, plus the theming and gesture handling that keep the whole thing from feeling janky.
Architectural fit: when to nest drawers and tabs
Combining navigation patterns is powerful, but it shouldn't be your default. Before writing any code, work out whether your app's UX actually benefits from a dual-navigation hierarchy. Three rough cases:
- Flat navigation (3 to 5 high-level screens with isolated paths): use pure bottom tabs. A side drawer here just adds cognitive load and visual clutter.
- Contextual isolation (a multi-tab workspace that needs omnipresent utilities like org switching or support): nest the tab navigator inside an outer drawer container.
- Deep-linked workflows (transaction-heavy dashboards where users bounce between deep sub-routes): reach for native stack navigation with context-driven visibility instead.
Architectural tradeoffs and limitations
Clean file-system layout paths ease structural complexity, but a few constraints are worth knowing about:
- State synchronization: Passing shared global state or active user details from a standalone drawer route (like a detached
settings.tsx) down into the nested tab context takes deliberate context scoping, or an external store. - Platform gesture conflicts: Side-swipe drawer gestures can conflict with horizontal elements inside your tabs, like swipeable carousels or swipe-to-delete lists. Restricting
swipeEdgeWidthkeeps those interactions predictable.
The core architecture
To keep navigation state unified, your project directory should mirror your layout tree. Instead of a centralized routing config file, Expo Router lets you declare the view hierarchy directly with folder grouping semantics like (drawer) and (tabs).
1. Root orchestration (app/_layout.tsx)
The root file is where the app lifecycle starts. It manages the native splash screen and sets up global context providers, so runtime configuration is in place before any child route renders.
2. Global side drawer implementation (app/(drawer)/_layout.tsx)
The side drawer is the outermost layout boundary. It uses the expo-router/drawer module for configuration. Setting drawerType to 'front' makes the drawer panel overlay the active screen instead of pushing it aside.
3. Nested bottom tab layout (app/(drawer)/(tabs)/_layout.tsx)
Nesting the tab layout folder inside the (drawer) path means child routes inherit the drawer context automatically. Micro-interactions live in custom components, which keeps the layout files focused on routing.
4. UI layer micro-interactions (src/components/AnimatedTabIcon.tsx)
Animations run only where they matter. With shared values from React Native Reanimated, transforms and opacity changes execute on the UI thread. Each micro-interaction stays isolated, so a tab animation doesn't trigger re-renders across the parent layout.
5. Scalable theme architecture: eliminating flash of unstyled content
To apply theme changes across nested boundaries without a flash of unstyled content, we use design tokens. Primitive values map to a semantic theme type, so components, navigation styles, and layouts all pull from one schema synchronously.
Summary and key framework documentation
Nesting navigation like this is mostly an exercise in discipline. Pushing routing config into the file system with Expo Router keeps layouts scaling cleanly, without a big configuration file to maintain.
For more on configuration and styling options, see the official docs:
- Expo Router directory grouping: how route groups nest, in the Expo Router introduction.
- Customizing the drawer: all the drawer options in the Expo Router drawer guide.
- React Navigation tab bar options: the full list in the bottom tab navigator docs.
Where to go next
Start with the boundary. Get (drawer)/_layout.tsx rendering, then nest the (tabs) group inside it and confirm both layouts show up before you touch theming or animations. Add the gesture constraints last, once the structure holds. Building it in that order keeps you from debugging a swipe conflict and a routing bug at the same time.
If you build something with this pattern, or hit an edge case we didn't cover, come share it in the Expo Discord. We hang out in the Expo Router channels and read the reports.


