--- title: Dropdown Menu description: An unstyled component for Jetpack Compose and Compose Multiplatform that can be used to implement Dropdown Menus with the styling of your choice. Fully accessible, supports keyboard navigation and open/close animations. --- {{unstyled_demo:dropdown-menu}} ## Basic Example There are four components that you will use to implement a dropdown: `Menu`, `MenuButton`, `MenuContent` and `MenuItem`. The `Menu` wraps the `MenuButton` and the `MenuContent` components. When the `MenuButton` is clicked, the `MenuContent` will be displayed on the screen at the position relative to the `Menu`. The `MenuContent` component wraps multiple `MenuItem`s. When a `MenuItem` is clicked, the menu is dismissed. Each `MenuItem` has a `onClick` parameter you can use for interaction purposes. The menu's dropdown visibility is handled for you thanks to the `Menu`'s internal state. ```kotlin val options = listOf("United States", "Greece", "Indonesia", "United Kingdom") var selected by remember { mutableStateOf(0) } val state = rememberMenuState(expanded = true) Column(Modifier.fillMaxSize()) { Menu(state, modifier = Modifier.align(Alignment.End)) { MenuButton( Modifier.clip(RoundedCornerShape(6.dp)) .border(1.dp, Color(0xFFBDBDBD), RoundedCornerShape(6.dp)) ) { Row( modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp), verticalAlignment = Alignment.CenterVertically, ) { Text("Options", style = defaultTextStyle.copy(fontWeight = FontWeight(500))) Spacer(Modifier.width(4.dp)) Image(ChevronDown, null) } } MenuContent( modifier = Modifier.width(320.dp) .border(1.dp, Color(0xFFE0E0E0), RoundedCornerShape(4.dp)) .background(Color.White) .padding(4.dp), exit = fadeOut() ) { options.forEachIndexed { index, option -> MenuItem( modifier = Modifier.clip(RoundedCornerShape(4.dp)), onClick = { selected = index } ) { Text(option, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 4.dp)) } } } } Text("Selected = ${options[selected]}") } ``` ## Code Examples ### Toggling the Menu Pass your own `MenuState` to the `Menu` and change the *expanded* property according to your needs: ```kotlin val state = rememberMenuState(expanded = true) Menu(state = state) { MenuButton { Text("Toggle the menu") } MenuContent { MenuItem(onClick = { state.expanded = false }) { Text("Close this menu") } } } ``` ### Positioning the menu This option is useful if you want to left align, center align or right align the `MenuButton` and the `MenuContent` when expanded. ```kotlin Menu { MenuButton { Text("Toggle the menu") } MenuContent(alignment = Alignment.End) { MenuItem(onClick = { /* TODO */ }) { Text("Option") } } } ``` ## Styling By default, the Menu component comes with no styling. This is by design as it is intended to be used as a building block for your own design system's menus. The `Menu` composable is used as an anchor point. Do not pass any styling to its `modifier`. Instead, use its `modifier` parameter for anchoring and positioning needs (such as `Modifier.align()`). The `MenuButton` is the composable responsible to handle clicking into showing and hiding the dropdown menu. The following sample shows the minimum setup you need to display something on the screen: ```kotlin Menu { MenuButton { Text("Show Options") } MenuContent { MenuItem(onClick = { /* TODO handle click */ }) { Text("Option 1") } MenuItem(onClick = { /* TODO handle click */ }) { Text("Option 2") } MenuItem(onClick = { /* TODO handle click */ }) { Text("Option 3") } } } ``` However, the result will not look pretty. The following section goes over how to style each component to achieve the visual results you want. ### Styling the Menu Button Pass the desired styling to the `MenuButton`'s `modifier`. Do not pass any padding to it, as the `MenuButton` handles click events internally and this will affect the interaction bounds. Instead, provide any content padding to the contents of the button instead: ```kotlin hl_lines="2 3 4" Menu { MenuButton(Modifier.clip(RoundedCornerShape(6.dp)).border(1.dp, Color(0xFFBDBDBD), RoundedCornerShape(6.dp))) { Text("Options", modifier = Modifier.padding(vertical = 8.dp, horizontal = 4.dp)) } MenuContent { MenuItem(onClick = { /* TODO handle click */ }) { Text("Option 1") } MenuItem(onClick = { /* TODO handle click */ }) { Text("Option 2") } MenuItem(onClick = { /* TODO handle click */ }) { Text("Option 3") } } } ``` ### Styling the MenuContent The `MenuContent` component is a layout on which the menu's items will be displayed when the menu is expanded. In Material Design this is often a card. ```kotlin hl_lines="6 7 8 9 10" Menu { MenuButton(Modifier.clip(RoundedCornerShape(6.dp)).border(1.dp, Color(0xFFBDBDBD), RoundedCornerShape(6.dp))) { Text("Options", modifier = Modifier.padding(vertical = 8.dp, horizontal = 4.dp)) } MenuContent(Modifier.width(320.dp).border(1.dp, Color(0xFFE0E0E0), RoundedCornerShape(4.dp)).background(Color.White).padding(4.dp)) { MenuItem(onClick = { selected = index }) { Text(option, modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp, horizontal = 4.dp)) } } } ``` ## Animating the Menu Modify the `showTransition` and `hideTransition` parameters of the `MenuContent` component to modify the animation specs of the dropdown menu when it is visible/hidden. The `MenuContent` use the `AnimatedVisiblity` composable internally, which gives you a lot of flexibility towards what you can achieve. ### Animation Recipes #### Material Design Dropdown Material Design scales and fades the dropdown in and out. ```kotlin hl_lines="3 4" MenuContent( modifier = Modifier.width(320.dp).border(1.dp, Color(0xFFE0E0E0), RoundedCornerShape(4.dp)).background(Color.White).padding(4.dp), enter = scaleIn(tween(durationMillis = 120, easing = LinearOutSlowInEasing), initialScale = 0.8f, transformOrigin = TransformOrigin(0f, 0f)) + fadeIn(tween(durationMillis = 30)), exit = scaleOut(tween(durationMillis = 1, delayMillis = 75), targetScale = 1f) + fadeOut(tween(durationMillis = 75)) ) { MenuItem(onClick = { /* TODO */ }) { Text("Option 1") } MenuItem(onClick = { /* TODO */ }) { Text("Option 2") } } ``` #### Mac OS Menu macOS shows the menu instantly on click, and quickly fades the menu out when dismissed: ```kotlin hl_lines="1" MenuContent(exit = fadeOut(tween(durationMillis = 100, easing = LinearEasing))) { MenuItem(onClick = { /* TODO */ }) { Text("Option 1") } } ``` ## Styling touch presses and focus `MenuItem`'s uses the default Compose mechanism for providing touch and focus feedback. Use the `LocalIndication` CompositionLocal to override the default indication. Here is an example of using Material Design's signature ripple feedback with your menu: ```kotlin import androidx.compose.foundation.LocalIndication import androidx.compose.material.ripple.rememberRipple CompositionLocalProvider(LocalIndication provides rememberRipple()) { // MenuButton and MenuContent will use the ripple effect when focused and pressed Menu { // TODO implement the rest of the menu } } ``` ## Keyboard Interactions | Key | Description | |---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------| |