Build apps faster with over 150+ styled components and screens! Check it out β†’

ModalWideNavigationRail

Common

Component in Material 3 Compose

Material design modal wide navigation rail.

Wide navigation rails provide access to primary destinations in apps when using tablet and desktop screens.

The modal wide navigation rail should be used to display multiple [WideNavigationRailItem]s, each representing a singular app destination, and, optionally, a header containing a menu button, a [FloatingActionButton], and/or a logo. Each destination is typically represented by an icon and a text label.

The [ModalWideNavigationRail] when collapsed behaves like a collapsed [WideNavigationRail]. When expanded, the modal wide navigation rail blocks interaction with the rest of an app’s content with a scrim. It is elevated above the app’s UI and doesn't affect the screen’s layout grid. That can be achieved like so:

@sample androidx.compose.material3.samples.ModalWideNavigationRailSample

For a dismissible [ModalWideNavigationRail], that enters from offscreen instead of expanding from the collapsed rail, set [hideOnCollapse] to true. That can be achieved like so:

@sample androidx.compose.material3.samples.DismissibleModalWideNavigationRailSample

See [WideNavigationRailItem] for configuration specific to each item, and not the overall [ModalWideNavigationRail] component.

Last updated:

Installation

dependencies {
   implementation("androidx.compose.material3:material3:1.4.0-alpha07")
}

Overloads

@ExperimentalMaterial3ExpressiveApi
@Composable
fun ModalWideNavigationRail(
    modifier: Modifier = Modifier,
    state: WideNavigationRailState = rememberWideNavigationRailState(),
    hideOnCollapse: Boolean = false,
    collapsedShape: Shape = WideNavigationRailDefaults.containerShape,
    expandedShape: Shape = WideNavigationRailDefaults.modalContainerShape,
    colors: WideNavigationRailColors = WideNavigationRailDefaults.colors(),
    header: @Composable (() -> Unit)? = null,
    expandedHeaderTopPadding: Dp = 0.dp,
    windowInsets: WindowInsets = WideNavigationRailDefaults.windowInsets,
    arrangement: Arrangement.Vertical = WideNavigationRailDefaults.arrangement,
    expandedProperties: ModalWideNavigationRailProperties =
        ModalWideNavigationRailDefaults.Properties,
    content: @Composable () -> Unit
)

Parameters

namedescription
modifierthe [Modifier] to be applied to this wide navigation rail
statethe [WideNavigationRailState] of this wide navigation rail
hideOnCollapsewhether this wide navigation rail should slide offscreen when it collapses and be hidden, or stay on screen as a collapsed wide navigation rail (default)
collapsedShapethe shape of this wide navigation rail's container when it's collapsed
expandedShapethe shape of this wide navigation rail's container when it's expanded
colors[WideNavigationRailColors] that will be used to resolve the colors used for this wide navigation rail. See [WideNavigationRailDefaults.colors]
headeroptional header that may hold a [FloatingActionButton] or a logo
expandedHeaderTopPaddingthe padding to be applied to the top of the rail. It's usually needed in order to align the content of the rail between the collapsed and expanded animation
windowInsetsa window insets of the wide navigation rail
arrangementthe [Arrangement.Vertical] of this wide navigation rail
expandedProperties[ModalWideNavigationRailProperties] for further customization of the expanded modal wide navigation rail's window behavior
contentthe content of this modal wide navigation rail, usually [WideNavigationRailItem]s

Code Examples

ModalWideNavigationRailSample

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun ModalWideNavigationRailSample() {
    var selectedItem by remember { mutableIntStateOf(0) }
    val items = listOf("Home", "Search", "Settings")
    val selectedIcons = listOf(Icons.Filled.Home, Icons.Filled.Favorite, Icons.Filled.Star)
    val unselectedIcons =
        listOf(Icons.Outlined.Home, Icons.Outlined.FavoriteBorder, Icons.Outlined.StarBorder)
    val state = rememberWideNavigationRailState()
    val scope = rememberCoroutineScope()

    Row(Modifier.fillMaxWidth()) {
        ModalWideNavigationRail(
            state = state,
            // Note: the value of expandedHeaderTopPadding depends on the layout of your screen in
            // order to achieve the best alignment.
            expandedHeaderTopPadding = 64.dp,
            header = {
                IconButton(
                    modifier =
                        Modifier.padding(start = 24.dp).semantics {
                            // The button must announce the expanded or collapsed state of the rail
                            // for accessibility.
                            stateDescription =
                                if (state.currentValue == WideNavigationRailValue.Expanded) {
                                    "Expanded"
                                } else {
                                    "Collapsed"
                                }
                        },
                    onClick = {
                        scope.launch {
                            if (state.targetValue == WideNavigationRailValue.Expanded)
                                state.collapse()
                            else state.expand()
                        }
                    }
                ) {
                    if (state.targetValue == WideNavigationRailValue.Expanded) {
                        Icon(Icons.AutoMirrored.Filled.MenuOpen, "Collapse rail")
                    } else {
                        Icon(Icons.Filled.Menu, "Expand rail")
                    }
                }
            }
        ) {
            items.forEachIndexed { index, item ->
                WideNavigationRailItem(
                    railExpanded = state.targetValue == WideNavigationRailValue.Expanded,
                    icon = {
                        Icon(
                            if (selectedItem == index) selectedIcons[index]
                            else unselectedIcons[index],
                            contentDescription = item
                        )
                    },
                    label = { Text(item) },
                    selected = selectedItem == index,
                    onClick = { selectedItem = index }
                )
            }
        }

        val textString =
            if (state.currentValue == WideNavigationRailValue.Expanded) {
                "Expanded"
            } else {
                "Collapsed"
            }
        Column {
            Text(modifier = Modifier.padding(16.dp), text = "The rail is $textString.")
            Text(
                modifier = Modifier.padding(16.dp),
                text =
                    "Note: The orientation of this demo has been locked to portrait mode, because" +
                        " landscape mode may result in a compact height in certain devices. For" +
                        " any compact screen dimensions, use a Navigation Bar instead."
            )
        }

        // Lock the orientation for this demo as the navigation rail may look cut off in landscape
        // in smaller screens.
        val context = LocalContext.current
        DisposableEffect(context) {
            (context as? Activity)?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
            onDispose {
                (context as? Activity)?.requestedOrientation =
                    ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
            }
        }
    }
}

DismissibleModalWideNavigationRailSample

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun DismissibleModalWideNavigationRailSample() {
    var selectedItem by remember { mutableIntStateOf(0) }
    val items = listOf("Home", "Search", "Settings")
    val selectedIcons = listOf(Icons.Filled.Home, Icons.Filled.Favorite, Icons.Filled.Star)
    val unselectedIcons =
        listOf(Icons.Outlined.Home, Icons.Outlined.FavoriteBorder, Icons.Outlined.StarBorder)
    val state = rememberWideNavigationRailState()
    val scope = rememberCoroutineScope()

    Row(Modifier.fillMaxSize()) {
        ModalWideNavigationRail(state = state, hideOnCollapse = true) {
            items.forEachIndexed { index, item ->
                WideNavigationRailItem(
                    railExpanded = true,
                    icon = {
                        Icon(
                            if (selectedItem == index) selectedIcons[index]
                            else unselectedIcons[index],
                            contentDescription = null
                        )
                    },
                    label = { Text(item) },
                    selected = selectedItem == index,
                    onClick = {
                        selectedItem = index
                        scope.launch { state.collapse() }
                    }
                )
            }
        }

        Column(Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
            val currentPage = items.get(selectedItem)
            Button(onClick = { scope.launch { state.expand() } }, Modifier.padding(32.dp)) {
                Text(text = "$currentPage Page\nOpen modal rail", textAlign = TextAlign.Center)
            }
        }
    }
}
by @alexstyl