Menu
A temporary list of choices or actions that opens from a button or a field.
- Category: Selection and input
- API status: Expressive
- Compose APIs:
DropdownMenu,DropdownMenuPopup,DropdownMenuGroup,DropdownMenuItem,ExposedDropdownMenuBox,MenuDefaults - Interactive page: https://m3e.ljzapps.app/#components/menu
When to use
Use a dropdown menu for actions related to an element, an exposed dropdown to choose a value in a form, and the grouped expressive menu when actions belong to distinct sections.
Menus close when an item is chosen, when the user clicks outside or presses Escape.
Playground
The interactive page has a playground with live property controls for this component and generated code.
Examples
Standard
var expanded by remember { mutableStateOf(false) }
Box(Modifier.fillMaxWidth().height(220.dp), contentAlignment = Alignment.TopStart) {
IconButton(onClick = { expanded = true }) {
Icon(Icons.Rounded.MoreVert, contentDescription = "More options")
}
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DropdownMenuItem(
text = { Text("Edit") },
onClick = { expanded = false },
leadingIcon = { Icon(Icons.Rounded.Edit, contentDescription = null) },
)
DropdownMenuItem(
text = { Text("Delete") },
onClick = { expanded = false },
leadingIcon = { Icon(Icons.Rounded.Delete, contentDescription = null) },
)
}
}
Grouped, expressive
Items in separate rounded groups.
var expanded by remember { mutableStateOf(false) }
Box(Modifier.fillMaxWidth().height(300.dp), contentAlignment = Alignment.TopStart) {
IconButton(onClick = { expanded = true }) {
Icon(Icons.Rounded.MoreVert, contentDescription = "More options")
}
DropdownMenuPopup(expanded = expanded, onDismissRequest = { expanded = false }) {
val groups = listOf(
listOf("Edit" to Icons.Rounded.Edit, "Copy" to Icons.Rounded.ContentCopy),
listOf("Share" to Icons.Rounded.Share, "Delete" to Icons.Rounded.Delete),
)
groups.forEachIndexed { groupIndex, items ->
DropdownMenuGroup(shapes = MenuDefaults.groupShape(groupIndex, groups.size)) {
items.forEachIndexed { itemIndex, (label, icon) ->
DropdownMenuItem(
text = { Text(label) },
onClick = { expanded = false },
shape = MenuDefaults.itemShape(itemIndex, items.size).shape,
leadingIcon = { Icon(icon, contentDescription = null) },
)
}
}
}
}
}
Select
An exposed dropdown inside a text field.
val options = listOf("Light", "Dark", "System default")
var expanded by remember { mutableStateOf(false) }
var selected by rememberSaveable { mutableStateOf(options.last()) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = Modifier.widthIn(max = 320.dp),
) {
OutlinedTextField(
value = selected,
onValueChange = {},
readOnly = true,
label = { Text("Theme") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
)
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
options.forEach { option ->
DropdownMenuItem(
text = { Text(option) },
onClick = {
selected = option
expanded = false
},
)
}
}
}
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
expanded | Boolean | Whether the menu is showing. | |
onDismissRequest | () -> Unit | Called on outside click or Escape. | |
offset | DpOffset | DpOffset(0.dp, 0.dp) | Moves the popup relative to its anchor. |
DropdownMenuItem.leadingIcon | @Composable (() -> Unit)? | null | Icon before the text. |
Guidelines
Do:
- Group related items.
- Put destructive items last.
Don't:
- Do not nest menus more than one level.
- Do not use a menu for primary navigation.
Accessibility
- Menu items are focusable and navigable by arrow keys. Focus moves to the first item on open.
- Exposed dropdowns announce the expanded state of the field.
- Keep labels short, and add a trailing shortcut hint only when one exists.