WideNavigationRail
Component in Material 3 Compose
Material design wide navigation rail.
Wide navigation rails provide access to primary destinations in apps when using tablet and desktop screens.
The 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 [WideNavigationRail] is collapsed by default, but it also supports being expanded via a [WideNavigationRailState]. When collapsed, the rail should display three to seven navigation items. A simple example looks like:
@sample androidx.compose.material3.samples.WideNavigationRailCollapsedSample
When expanded, the rail should display at least three navigation items. A simple example looks like:
@sample androidx.compose.material3.samples.WideNavigationRailExpandedSample
The [WideNavigationRail] also supports automatically animating between the collapsed and expanded values. That can be done like so:
@sample androidx.compose.material3.samples.WideNavigationRailResponsiveSample
For a modal variation of the wide navigation rail, see [ModalWideNavigationRail].
Finally, the [WideNavigationRail] supports setting an [Arrangement.Vertical] for the items, with [Arrangement.Top] being the default. The header will always be at the top.
See [WideNavigationRailItem] for configuration specific to each item, and not the overall [WideNavigationRail] component.
Last updated:
Installation
dependencies {
implementation("androidx.compose.material3:material3:1.4.0-alpha07")
}
Overloads
@ExperimentalMaterial3ExpressiveApi
@Composable
fun WideNavigationRail(
modifier: Modifier = Modifier,
state: WideNavigationRailState = rememberWideNavigationRailState(),
shape: Shape = WideNavigationRailDefaults.containerShape,
colors: WideNavigationRailColors = WideNavigationRailDefaults.colors(),
header: @Composable (() -> Unit)? = null,
windowInsets: WindowInsets = WideNavigationRailDefaults.windowInsets,
arrangement: Arrangement.Vertical = WideNavigationRailDefaults.arrangement,
content: @Composable () -> Unit
)
Parameters
name | description |
---|---|
modifier | the [Modifier] to be applied to this wide navigation rail |
state | the [WideNavigationRailState] of this wide navigation rail |
shape | defines the shape of this wide navigation rail's container. |
colors | [WideNavigationRailColors] that will be used to resolve the colors used for this wide navigation rail. See [WideNavigationRailDefaults.colors] |
header | optional header that may hold a [FloatingActionButton] or a logo |
windowInsets | a window insets of the wide navigation rail |
arrangement | the [Arrangement.Vertical] of this wide navigation rail for its content. Note that if there's a header present, the items will be arranged on the remaining space below it, except for the center arrangement which considers the entire height of the container |
content | the content of this wide navigation rail, typically [WideNavigationRailItem]s |
Code Examples
WideNavigationRailCollapsedSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun WideNavigationRailCollapsedSample() {
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)
WideNavigationRail {
items.forEachIndexed { index, item ->
WideNavigationRailItem(
icon = {
Icon(
if (selectedItem == index) selectedIcons[index] else unselectedIcons[index],
contentDescription = null
)
},
label = { Text(item) },
selected = selectedItem == index,
onClick = { selectedItem = index }
)
}
}
}
WideNavigationRailExpandedSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun WideNavigationRailExpandedSample() {
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)
WideNavigationRail(
state = rememberWideNavigationRailState(initialValue = WideNavigationRailValue.Expanded)
) {
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 }
)
}
}
}
WideNavigationRailResponsiveSample
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun WideNavigationRailResponsiveSample() {
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()) {
WideNavigationRail(
state = state,
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 = {
val imageVector =
if (selectedItem == index) {
selectedIcons[index]
} else {
unselectedIcons[index]
}
Icon(imageVector = imageVector, contentDescription = null)
},
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 = "Is animating: " + state.isAnimating)
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
}
}
}