DEV Community

myougaTheAxo
myougaTheAxo

Posted on

NavigationDrawer in Compose: Modal & Permanent Side Menu Guide

NavigationDrawer in Compose: Modal & Permanent Side Menu Guide

Navigation drawers provide organized menu navigation, especially valuable on tablets. This guide covers ModalNavigationDrawer, PermanentNavigationDrawer, and adaptive layouts.

ModalNavigationDrawer: Phone-Style Drawer

The ModalNavigationDrawer slides from the left, overlaying content:

var drawerState by remember { mutableStateOf(DrawerState(DrawerValue.Closed)) }

ModalNavigationDrawer(
    drawerContent = {
        ModalDrawerSheet {
            Text("Menu", modifier = Modifier.padding(16.dp))
            Divider()
            NavigationDrawerItem(
                label = { Text("Home") },
                selected = true,
                onClick = { }
            )
            NavigationDrawerItem(
                label = { Text("Profile") },
                selected = false,
                onClick = { }
            )
        }
    }
) {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("App") },
                navigationIcon = {
                    IconButton(onClick = { }) {
                        Icon(Icons.Default.Menu, contentDescription = "Menu")
                    }
                }
            )
        }
    ) { padding ->
        Box(modifier = Modifier.padding(padding)) {
            Text("Main content")
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

rememberDrawerState: Managing State

Control drawer open/closed state:

val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

ModalNavigationDrawer(
    drawerState = drawerState,
    drawerContent = { /* ... */ }
) {
    Scaffold(
        topBar = {
            TopAppBar(
                navigationIcon = {
                    IconButton(
                        onClick = {
                            scope.launch {
                                drawerState.open()
                            }
                        }
                    ) {
                        Icon(Icons.Default.Menu, contentDescription = "Menu")
                    }
                }
            )
        }
    ) { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

PermanentNavigationDrawer: Tablet-Style Layout

For wider screens, use PermanentNavigationDrawer that stays visible:

@Composable
fun TabletLayout() {
    Row(modifier = Modifier.fillMaxSize()) {
        PermanentNavigationDrawer(
            drawerContent = {
                PermanentDrawerSheet {
                    Text("Menu", modifier = Modifier.padding(16.dp))
                    Divider()
                    NavigationDrawerItem(
                        label = { Text("Home") },
                        selected = true,
                        onClick = { }
                    )
                    NavigationDrawerItem(
                        label = { Text("Settings") },
                        selected = false,
                        onClick = { }
                    )
                }
            }
        ) {
            Box(modifier = Modifier.weight(1f)) {
                Text("Main content")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

NavigationDrawerItem Customization

Customize drawer items with icons and badges:

NavigationDrawerItem(
    label = { Text("Notifications") },
    icon = { Icon(Icons.Default.Notifications, contentDescription = null) },
    selected = isSelected,
    onClick = { },
    badge = { Badge { Text("5") } },
    modifier = Modifier.padding(horizontal = 12.dp)
)
Enter fullscreen mode Exit fullscreen mode

Adaptive Drawer with WindowSizeClass

Switch between modal and permanent based on screen size:

@Composable
fun AdaptiveNavigationDrawer(windowSizeClass: WindowSizeClass) {
    when (windowSizeClass.widthSizeClass) {
        WindowWidthSizeClass.Compact, WindowWidthSizeClass.Medium -> {
            // Use ModalNavigationDrawer for phones/small tablets
            ModalNavigationDrawer(
                drawerContent = { /* ... */ }
            ) { /* content */ }
        }
        else -> {
            // Use PermanentNavigationDrawer for large tablets
            Row {
                PermanentNavigationDrawer(
                    drawerContent = { /* ... */ }
                ) { /* content */ }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

User Profile Header in Drawer

Add profile section at the top of the drawer:

@Composable
fun DrawerHeader(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier
            .fillMaxWidth()
            .background(MaterialTheme.colorScheme.primaryContainer)
            .padding(16.dp),
        verticalArrangement = Arrangement.Center
    ) {
        Surface(
            shape = CircleShape,
            modifier = Modifier.size(48.dp)
        ) {
            Image(
                painter = painterResource(id = R.drawable.profile),
                contentDescription = "Profile",
                contentScale = ContentScale.Crop
            )
        }
        Spacer(modifier = Modifier.height(12.dp))
        Text(
            text = "John Doe",
            style = MaterialTheme.typography.titleMedium,
            color = MaterialTheme.colorScheme.onPrimaryContainer
        )
        Text(
            text = "john@example.com",
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f)
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Complete Example

@Composable
fun AppWithDrawer() {
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    val scope = rememberCoroutineScope()
    var selectedItem by remember { mutableStateOf("Home") }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            ModalDrawerSheet {
                DrawerHeader()
                Divider()
                listOf("Home", "Profile", "Settings").forEach { item ->
                    NavigationDrawerItem(
                        label = { Text(item) },
                        selected = item == selectedItem,
                        onClick = {
                            selectedItem = item
                            scope.launch { drawerState.close() }
                        }
                    )
                }
            }
        }
    ) {
        Scaffold(
            topBar = {
                TopAppBar(
                    title = { Text(selectedItem) },
                    navigationIcon = {
                        IconButton(
                            onClick = { scope.launch { drawerState.open() } }
                        ) {
                            Icon(Icons.Default.Menu, contentDescription = "Menu")
                        }
                    }
                )
            }
        ) { padding ->
            Box(modifier = Modifier.padding(padding)) {
                Text("$selectedItem screen content")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use ModalNavigationDrawer for phones
  • Use PermanentNavigationDrawer on tablets
  • Implement WindowSizeClass for responsive layouts
  • Add profile/account header for context
  • Keep drawer items concise and well-organized
  • Smooth state transitions with coroutines

8 Android App Templates → https://myougatheax.gumroad.com

Top comments (0)