Android Bottom Navigation vs Navigation Drawer: How to Choose the Right Pattern
Your users are abandoning features they don’t know exist.
Not because the features are bad. Not because the onboarding is broken. Because those features are buried behind a hamburger menu that nobody taps. The navigation pattern you chose in week one of development is silently strangling your app’s engagement — and most teams never connect the dots.
Here’s the uncomfortable truth: hidden navigation reduces feature exploration by 30-40% compared to visible navigation. That’s not a rounding error. That’s a structural decision made during initial implementation that compounds over the entire life of your app.
Bottom navigation vs. navigation drawer isn’t a stylistic debate. It’s an architectural choice with real consequences for retention, discoverability, and the thumb anatomy of everyone who uses your app.
The Rule That Settles Most Debates
Material Design 3 is blunt about this:
- 3-5 primary destinations: Use bottom navigation
- 6+ destinations, or destinations with mixed hierarchy: Use a navigation drawer (or its MD3 successor, the expanded navigation rail)
That’s the baseline. Everything else is context.
The reason bottom navigation wins for smaller destination sets comes down to how humans hold phones. Thumb zone research consistently shows the bottom third of a screen is the most accessible area for single-handed use. Navigation items sitting 64dp from the bottom of the screen are reachable without repositioning your grip. A hamburger menu in the top-left corner requires either a two-handed grip or an uncomfortable stretch.
Your users are not sitting at a desk with two free hands. They’re on the subway. They’re walking. They’re holding a coffee. Design for how people actually use phones.
What “Primary Destination” Actually Means
Teams routinely miscount destinations because they conflate navigation hierarchy levels.
A primary destination is a top-level section of your app that users need to switch between frequently and independently. It is not a sub-screen within a section. It is not a settings screen. It is not something users access once during onboarding and never again.
Ask yourself: does a user need to jump to this destination mid-flow, without finishing what they’re currently doing? If yes, it’s a candidate for primary navigation. If it’s always reached by going deeper into something else, it’s secondary navigation and doesn’t belong in your bottom bar or drawer at all.
Misplacing secondary navigation in bottom nav creates a different problem: cognitive overload from too many visible options, combined with confusion about what each destination actually represents.
Good bottom navigation candidates:
- Home / Feed
- Search
- Inbox / Notifications
- Profile
- Explore / Discover
Bad bottom navigation candidates:
- Settings
- Help & Support
- Edit Profile
- Order History
Bottom Navigation: Implementation in Jetpack Compose
Here’s a clean implementation using Material 3’s NavigationBar:
@Composable
fun AppBottomNavigation(
currentRoute: String,
onNavigate: (String) -> Unit
) {
val items = listOf(
NavItem("home", Icons.Default.Home, "Home"),
NavItem("search", Icons.Default.Search, "Search"),
NavItem("inbox", Icons.Default.Inbox, "Inbox"),
NavItem("profile", Icons.Default.Person, "Profile")
)
NavigationBar(
containerColor = MaterialTheme.colorScheme.surface,
tonalElevation = 0.dp // MD3 uses color, not elevation for layering
) {
items.forEach { item ->
NavigationBarItem(
icon = {
Icon(
imageVector = item.icon,
contentDescription = item.label
)
},
label = { Text(item.label) },
selected = currentRoute == item.route,
onClick = { onNavigate(item.route) },
colors = NavigationBarItemDefaults.colors(
selectedIconColor = MaterialTheme.colorScheme.onSecondaryContainer,
selectedTextColor = MaterialTheme.colorScheme.onSurface,
indicatorColor = MaterialTheme.colorScheme.secondaryContainer,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant
)
)
}
}
}
data class NavItem(val route: String, val icon: ImageVector, val label: String)One detail worth calling out: tonalElevation = 0.dp. MD3 dropped the shadow-based elevation model for navigation bars. Color tonal values carry the visual hierarchy now. If you’re still setting high elevation values, you’re fighting the design system.
Navigation Drawer: When the Hamburger Menu is Actually Correct
Stop treating the navigation drawer as a legacy pattern. It’s the right choice in specific scenarios — the problem is that it gets misapplied everywhere.
Use a navigation drawer when:
- You have 6+ distinct top-level destinations that can’t be meaningfully reduced
- Your app has a document/project structure (think Google Drive, Notion, Slack workspaces)
- Navigation between destinations is infrequent — users go deep into one section and stay there
- You’re building for tablets or desktop-class screens where the drawer can be persistent
The critical distinction in MD3: navigation drawers are no longer recommended as the primary navigation pattern on phones. For phone-specific designs with many destinations, MD3 pushes toward a combination of bottom navigation for primary sections and a navigation rail (expanded on tablet/desktop) for secondary structure.
Gmail is the canonical example of getting this right. It uses bottom navigation for the primary mailbox views (Primary, Social, Promotions) and a drawer for account switching and folder navigation — two separate navigation concerns handled by two separate patterns.
Drawer Implementation: The MD3 Way
@Composable
fun AppWithNavigationDrawer() {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Spacer(modifier = Modifier.height(12.dp))
DrawerHeader()
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
drawerItems.forEach { item ->
NavigationDrawerItem(
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) },
selected = currentRoute == item.route,
onClick = {
scope.launch { drawerState.close() }
onNavigate(item.route)
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("App Name") },
navigationIcon = {
IconButton(onClick = {
scope.launch { drawerState.open() }
}) {
Icon(Icons.Default.Menu, "Open navigation")
}
}
)
}
) { padding ->
AppContent(modifier = Modifier.padding(padding))
}
}
}Note the gesture handling: ModalNavigationDrawer automatically handles edge swipe gestures to open the drawer. Don’t disable this unless you have a specific reason — it’s expected behavior on Android and users reach for it instinctively.
The Responsive Navigation Strategy
Here’s where many teams leave significant usability on the table: navigation patterns should adapt across screen sizes, not stay fixed.
Material Design 3 defines a clear responsive ladder:
| Screen Size | Width | Navigation Pattern |
|---|---|---|
| Compact (phone) | < 600dp | Bottom Navigation |
| Medium (tablet portrait) | 600-840dp | Navigation Rail |
| Expanded (tablet landscape, desktop) | > 840dp | Navigation Drawer (persistent) |
This isn’t just a tablet consideration. As foldable phones become more common — Galaxy Z Fold, Pixel Fold — your app will run in multiple configurations on the same device within the same session. A navigation pattern that ignores this creates jarring transitions when the user unfolds their phone.
@Composable
fun AdaptiveNavigation(windowSizeClass: WindowSizeClass) {
when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact -> {
// Bottom navigation for phones
BottomNavScaffold()
}
WindowWidthSizeClass.Medium -> {
// Navigation rail for tablets in portrait
NavRailScaffold()
}
WindowWidthSizeClass.Expanded -> {
// Persistent drawer for tablets in landscape / desktop
PersistentDrawerScaffold()
}
}
}The WindowSizeClass API from androidx.compose.material3.windowsizeclass makes this straightforward. Compute it once at the activity level and pass it down. Don’t recompute inside deeply nested composables.
Real App Patterns Worth Studying
Instagram runs a ViewPager behind its bottom navigation for swipe-to-switch behavior between primary tabs. The tabs reflect the current pager position, and swiping updates the selected tab. This reduces tapping friction significantly for users who switch between Feed and Reels constantly.
Google Maps uses a hybrid: a bottom sheet for location details and navigation controls, and a slide-out drawer for layers and settings. Neither is technically a “navigation” pattern in the traditional sense — Maps doesn’t have destination switching in the conventional way. The drawer there is a tool container, not a nav structure.
Spotify gets this mostly right: bottom nav for Home, Search, Your Library, and Premium. When library size grows, it expands within the Library tab using a filter bar rather than spawning new top-level destinations. One of the better examples of resisting destination creep.
The Three Pain Points Nobody Warns You About
1. Accidental Bottom Taps
Bottom navigation sits at the thumb’s natural resting position. Users tap it accidentally — especially when scrolling. The fix isn’t to move the nav bar up. The fix is ensuring that navigating to an already-selected tab doesn’t destroy scroll state. Scroll the list back to the top on re-selection, but preserve state when navigating away and returning.
// Preserve scroll state per tab using SaveableStateHolder
val saveableStateHolder = rememberSaveableStateHolder()
saveableStateHolder.SaveableStateProvider(key = currentRoute) {
CurrentScreenContent()
}2. Back Button Unpredictability
Android’s back gesture interacts with navigation in ways that surprise users and developers equally. With Jetpack Navigation, define your back stack behavior explicitly. Bottom nav items should typically pop to their own start destination on back press — not exit the app or go to a previous tab.
// In NavHost, set restoreState = true and saveState = true
navController.navigate(route) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}3. Deep Linking Into Non-Default Tabs
When a push notification or external link needs to open a specific screen inside a non-primary tab, your navigation stack needs to be constructed correctly or the back button behavior will be wrong. The user taps back from the deep-linked screen and ends up… somewhere unexpected.
Handle this by building the back stack synthetically when handling deep links:
navController.navigate(deepLinkRoute) {
popUpTo(navController.graph.startDestinationId) {
inclusive = false
}
}If you’re tracking which navigation patterns are actually improving user retention and conversion in your Android app, tools like AppBooster can surface which UX decisions are contributing to growth — particularly useful when you’re A/B testing navigation changes.
MD3 Deprecation: What Changed
A note on terminology that causes confusion: “navigation drawer” as the primary phone navigation pattern is deprecated in Material Design 3. This doesn’t mean drawers are gone — it means they’ve been repositioned.
The MD3 guidance explicitly states that the navigation drawer should not be used as the primary navigation component on phones. For apps that previously used a full drawer on phones, the recommended migration path is:
- Identify your 3-5 most-used destinations → move to bottom nav
- Secondary destinations → nest within relevant primary sections or use a contextual drawer
- Account switching, workspace switching → use a dedicated account picker pattern
The navigation rail (a vertical bar that sits on the left edge) replaces the drawer for medium-sized screens and becomes persistent on expanded screens. On phones, it’s not used at all — bottom nav handles that role.
Decision Framework: Choose in 5 Questions
- How many primary destinations? Under 5 → bottom nav. Over 6 → drawer or expanded rail.
- How often do users switch between top-level sections? Frequently → bottom nav (always visible). Rarely → drawer is acceptable.
- What screen sizes does your app target? Phone-only → bottom nav + no drawer. Adaptive → bottom nav + rail + drawer.
- Does your app have workspace/account switching? Yes → drawer for that specific concern, separate from primary nav.
- Are you following MD3? Yes → navigation drawer on phones is not the primary pattern.
There’s no navigation pattern that works for every app. But there is a wrong pattern for your specific destination count, usage frequency, and screen size targets — and the framework above makes it identifiable without guesswork.
The Bottom Line
Navigation architecture is one of those decisions that feels low-stakes at the start and turns into a significant refactor once your app has 100,000 users and muscle memory has formed.
The rule is simple: visible navigation drives exploration; hidden navigation suppresses it. If your feature set matters — and it does, or you wouldn’t have built it — put it somewhere users can see it.
Bottom navigation wins for 3-5 primary destinations on phones, full stop. Navigation drawers have a legitimate role in complex apps with many destinations, account switching, or non-phone targets. The responsive ladder from bottom nav to rail to persistent drawer handles everything in between.
Get this right early. The cost of changing navigation patterns after launch is measured in user retraining, muscle memory breakage, and the inevitable one-star reviews complaining that “the app changed and now I can’t find anything.”
For teams tracking how navigation changes affect real growth metrics — installs, retention, conversion — AppBooster provides the analytics layer that connects UX decisions to business outcomes.
Build it visible. Build it adaptive. Build it once.
Share this article
Build better extensions with free tools
Icon generator, MV3 converter, review exporter, and more — no signup needed.
Related Articles
Android Adaptive Layouts: Your App on Foldables, Tablets, and Everything in Between
300M+ large-screen Android devices. Android 17 mandates adaptive support. Window size classes, canonical layouts, and foldable posture handling.
Android App Onboarding UX: 7 Patterns That Cut Churn by 50%
97.9% of Android users churn by Day 30. These onboarding UX patterns from Duolingo, Headspace, and Notion fight back with data-proven results.
Android Deep Linking Best Practices for App Growth in 2026
Firebase Dynamic Links shut down in Aug 2025 — 30% of deep links are now failing. Here's how to fix them and drive 2-3x higher conversions.