Navigation Drawer using Jetpack Compose

Navigation Drawer using Jetpack Compose

What is Navigation Drawer?

Navigation Drawer is the most important components of Material Design. There are two types of navigation drawers: standard and modal. 

Navigation Drawer is a sliding left menu that is used to display the important links in the application. Navigation drawer makes it easy to navigate to and fro between those links. It’s not visible by default and it needs to opened either by sliding from left or clicking its icon in the Action Bar.

In this tutorial we will learn to create a simple Modal Navigation Drawer. Here we are using Android Studio Koala version.

How to create Navigation rail

Dependency wee need:

implementation("androidx.navigation:navigation-compose-android:2.9.0")

Before diving in, make sure you have:

  1. Android Studio Meerkat or above
  2. Kotlin 1.9+
  3. Jetpack Compose set up in your project
If you’re starting fresh, create a new project and choose Empty Compose Activity.

🧱 What We'll Build

We’ll create an app with:
  • A Navigation Drawer Layout using ModalNavigationDrawer
  • A Top App Bar to open the drawer
  • A List of Navigation Items
  • A modern, beautiful UI with icons and drawer header

MainActivity

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
BasicOfJetpackComposeIn30DaysTheme {

NavDrawer()

}
}
}
}

NavDrawer


@Composable
fun NavDrawer() {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
modifier = Modifier
.fillMaxHeight()
.clip(RoundedCornerShape(topEnd = 24.dp, bottomEnd = 24.dp))
.background(MaterialTheme.colorScheme.surface)
) {
// Drawer Header
Row(
modifier = Modifier
.padding(20.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "My Drawer",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
IconButton(onClick = {
scope.launch { drawerState.close() }
}) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close Drawer",
tint = MaterialTheme.colorScheme.onSurface
)
}
}

HorizontalDivider()

// Drawer Items
val drawerItems = listOf("Home", "Profile", "Settings", "Logout")
drawerItems.forEachIndexed { index, item ->
NavigationDrawerItem(
label = { Text(text = item) },
selected = false,
onClick = { },
modifier = Modifier.padding(horizontal = 12.dp),
icon = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null
)
}
)
}
}
}
) {
// Open Drawer Button
IconButton(
onClick = {
scope.launch {
if (drawerState.isClosed) drawerState.open()
else drawerState.close()
}
},
modifier = Modifier
.padding(18.dp)
.background(MaterialTheme.colorScheme.primaryContainer, CircleShape)
.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Open Drawer",
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}

// Main Content
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
listOf(
MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.background
)
)
),
contentAlignment = Alignment.Center
) {
Card(
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(8.dp),
modifier = Modifier.padding(32.dp)
) {
Text(
text = "Welcome to the Modern UI!",
modifier = Modifier.padding(24.dp),
style = MaterialTheme.typography.headlineSmall
)
}
}
}
}

OUTPUT:

Navigation Drawer using Jetpack Compose

If Navigation Drawer is open and we click back button it directly exit the app not close the Drawer, so in the final code we will use the Back Handler, Top App bar using  Scaffold ( Experimental )and also add a menu to open and a close icon to close the Drawer.
// Close drawer on back press
if (drawerState.isOpen) {
BackHandler {
scope.launch {
drawerState.close()
}
}
}

Final Code:


@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavDrawer() {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

// Close drawer on back press
if (drawerState.isOpen) {
BackHandler {
scope.launch { drawerState.close() }
}
}

ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
modifier = Modifier
.fillMaxHeight()
.clip(RoundedCornerShape(topEnd = 24.dp, bottomEnd = 24.dp))
.background(MaterialTheme.colorScheme.surface)
) {
// Drawer Header
Row(
modifier = Modifier
.padding(20.dp)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "My Drawer",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
}
IconButton(onClick = {
scope.launch { drawerState.close() }
}) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "Close Drawer",
tint = MaterialTheme.colorScheme.onSurface
)
}
}

HorizontalDivider()

// Drawer Items
val drawerItems = listOf("Home", "Profile", "Settings", "Logout")
drawerItems.forEach { item ->
NavigationDrawerItem(
label = { Text(text = item) },
selected = false,
onClick = { /* Handle item click */ },
modifier = Modifier.padding(horizontal = 12.dp),
icon = {
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null
)
}
)
}
}
}
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Modern UI") },
navigationIcon = {
IconButton(
onClick = {
scope.launch {
if (drawerState.isClosed) drawerState.open()
else drawerState.close()
}
}
) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "Toggle Drawer"
)
}
}
)
}
) { innerPadding ->
Box(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.background(
Brush.verticalGradient(
listOf(
MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.background
)
)
),
contentAlignment = Alignment.Center
) {
Card(
shape = RoundedCornerShape(16.dp),
elevation = CardDefaults.cardElevation(8.dp),
modifier = Modifier.padding(32.dp)
) {
Text(
text = "Welcome to the Modern UI!",
modifier = Modifier.padding(24.dp),
style = MaterialTheme.typography.headlineSmall
)
}
}
}
}
}

Final Result:

Final Result of Navigation Drawer using Jetpack Compose

Final Result of Navigation Drawer using Jetpack Compose

🎉 Done! What You’ve Learned

By now, you’ve built a full navigation drawer setup in Jetpack Compose:
  1. Custom drawer layout with top bar
  2. Seamless navigation between screens
  3. Icon support and drawer header
  4. Modular, scalable Compose architecture


Navigation between screens using Navigation Component for Compose

🧩 Step 1: Add Dependencies

Make sure your build.gradle(:app) includes the navigation dependency:
implementation("androidx.navigation:navigation-compose-android:2.9.0")

🧭 Step 2: Create a Navigation Drawer Item Data Class

Let’s define what each item in the drawer represents.
data class DrawerItem(
val route: String,
val icon: ImageVector,
val title: String
)

🎨 Step 3: Define Navigation Destinations

Create a sealed class or enum to manage your routes better:
sealed class Screen(val route: String) {
object Home : Screen("home")
object Profile : Screen("profile")
object Settings : Screen("settings")
}

🗂️ Step 4: Create Screens

Now create Composables for your screens:

@Composable
fun HomeScreen() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("🏠 Home Screen", style = MaterialTheme.typography.headlineMedium)
}
}

@Composable
fun ProfileScreen() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("👤 Profile Screen", style = MaterialTheme.typography.headlineMedium)
}
}

@Composable
fun SettingsScreen() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text("⚙️ Settings Screen", style = MaterialTheme.typography.headlineMedium)
}
}

📦 Step 5: Create Navigation Host

Set up a Navigation Controller to manage routing:
@Composable
fun AppNavigation(navController: NavHostController) {
NavHost(navController, startDestination = Screen.Home.route) {
composable(Screen.Home.route) { HomeScreen() }
composable(Screen.Profile.route) { ProfileScreen() }
composable(Screen.Settings.route) { SettingsScreen() }
}
}

🧱 Step 6: Build the Drawer UI

Now let’s build the core UI with a drawer.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreen() {
val navController = rememberNavController()
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()

val items = listOf(
DrawerItem("Home", Icons.Default.Home, Screen.Home.route),
DrawerItem("Profile", Icons.Default.Person, Screen.Profile.route),
DrawerItem("Settings", Icons.Default.Settings, Screen.Settings.route),
)

ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Column(modifier = Modifier.fillMaxSize()) {
DrawerHeader()
items.forEach { item ->
NavigationDrawerItem(
label = { Text(item.title) },
icon = { Icon(item.icon, contentDescription = item.title) },
selected = false,
onClick = {
scope.launch {
drawerState.close()
}
navController.navigate(item.route)
},
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
)
}
}
}
) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Jetpack Compose Drawer") },
navigationIcon = {
IconButton(onClick = {
scope.launch { drawerState.open() }
}) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
)
}
) {
Box(modifier = Modifier.padding(it)) {
AppNavigation(navController)
}
}
}
}

🖼️ Step 7: Add a Custom Drawer Header

Make your drawer look more personalized and beautiful.
@Composable
fun DrawerHeader() {
Box(
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primaryContainer)
.padding(24.dp),
contentAlignment = Alignment.CenterStart
) {
Column {
Text("Hello, Developer!", style = MaterialTheme.typography.headlineSmall)
Text("explore the app", style = MaterialTheme.typography.bodyMedium)
}
}
}
📱 Final Setup in MainActivity.kt
class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
BasicOfJetpackComposeIn30DaysTheme {

MainScreen()

}
}
}
}

Final Output:

Final Output: Navigation Drawer using Jetpack Compose

Building a Navigation Drawer in Jetpack Compose is not only cleaner but way more intuitive than using XML layouts and fragments. Thanks to the power of Kotlin and composables, you can quickly build beautiful, reactive UIs that feel native and modern.

You now have a solid template to use in any app requiring a sidebar-style menu. Keep experimenting — add colors, animations, or even badges for notifications!

Want to explore more UI patterns like bottom navigation, tabs, or animated drawers? Stay tuned for the next tutorial.
Previous Post Next Post

Contact Form