
In this article, we’ll learn how to implement the dropdown menu APIs in Material 3 Jetpack Compose.
Prerequisites:
What is a Dropdown Menu?
It is used to display a list of choices, similar to a pop-up menu.
There are two dropdown menu APIs:
- Dropdown Menu
- Exposed Dropdown Menu
1. Dropdown Menu:
This is a normal menu.
Example:

2. Exposed Dropdown Menu:
It displays the currently selected item.
Example:

Let’s see how to implement them in the Android Studio.
First, create an empty Compose project and open MainActivity. Create a Composable called MyUI() and call it from the onCreate(). We’ll write our code in it.
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material.icons.outlined.Email
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
class MainActivity1 : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyUI()
}
}
}
}
}
}
@Composable
fun MyUI() {
}
1. Dropdown Menu:
Jetpack Compose provides DropdownMenu() method:
@Composable
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit
)
expanded – Whether the menu is visible or not.
onDismissRequest – It is called when the user taps outside the menu’s bounds or presses the back button.
modifier – Modifier to be applied to the menu’s content.
offset – It is used to adjust the position of the menu on the screen.
scrollState – Scroll state of the menu.
properties – Used for customizing the popup’s behavior.
content – The content of this menu. Here, we add items using the DropdownMenuItem() method. It looks like this:
@Composable
fun DropdownMenuItem(
text: @Composable () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
colors: MenuItemColors = MenuDefaults.itemColors(),
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
)
text – Name of the item.
onClick – It is called when this item is clicked.
modifier – Modifier to be applied to this menu item.
leadingIcon – Optional leading icon to be displayed at the beginning of the item’s text.
trailingIcon – Optional trailing icon to be displayed at the end of the item’s text. It can also accept Text() to indicate a keyboard shortcut.
enabled – If the item is enabled or not. When false, this component will not respond to user input, and it will appear visually disabled and disabled to accessibility services.
colors – Colors of the menu item in different states.
contentPadding – The padding applied to the content of this menu item.
interactionSource – We can observe and customize the interactions. For example, we can disable the ripple effect.
Example:
@Composable
fun MyUI() {
var expanded by remember { mutableStateOf(false) }
val contextForToast = LocalContext.current.applicationContext
Box(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(align = Alignment.Center),
contentAlignment = Alignment.Center
) {
// vertical 3 dots icon
IconButton(
onClick = {
expanded = true
}
) {
Icon(Icons.Default.MoreVert, contentDescription = "Open Menu")
}
// menu
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
// menu items
DropdownMenuItem(
text = {
Text("Edit")
},
onClick = {
Toast.makeText(contextForToast, "Edit", Toast.LENGTH_SHORT).show()
expanded = false
},
leadingIcon = {
Icon(
Icons.Outlined.Edit,
contentDescription = null
)
}
)
DropdownMenuItem(
text = {
Text("Settings")
},
onClick = {
Toast.makeText(contextForToast, "Settings", Toast.LENGTH_SHORT).show()
expanded = false
},
leadingIcon = {
Icon(
Icons.Outlined.Settings,
contentDescription = null
)
}
)
DropdownMenuItem(
text = {
Text("Send Feedback")
},
onClick = {
Toast.makeText(contextForToast, "Send Feedback", Toast.LENGTH_SHORT).show()
expanded = false
},
leadingIcon = {
Icon(
Icons.Outlined.Email,
contentDescription = null
)
}
)
}
}
}
Output:

Scrollable Dropdown Menu:
In the Material 3 version 1.2.0-alpha02, scrollState parameter was added to the DropdownMenu(). It helps us to create a scrollable menu.
@Composable
fun MyUI() {
var expanded by remember { mutableStateOf(false) }
val contextForToast = LocalContext.current.applicationContext
val scrollState = rememberScrollState()
Box(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center),
contentAlignment = Alignment.Center
) {
// vertical 3 dots button
IconButton(
onClick = {
expanded = true
}
) {
Icon(Icons.Default.MoreVert, contentDescription = "Open Menu")
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
scrollState = scrollState
) {
repeat(30) {
DropdownMenuItem(
text = {
Text("Item $it")
},
onClick = {
Toast.makeText(contextForToast, "Item $it", Toast.LENGTH_SHORT).show()
expanded = false
}
)
}
}
}
LaunchedEffect(expanded) {
if (expanded) {
// scroll to the last item
scrollState.scrollTo(scrollState.maxValue)
}
}
}
Output:

Use the scrollTo() method to jump to any position.
2. Exposed Dropdown Menu:
It displays the currently selected item. We can implement it with the following APIs:
- ExposedDropdownMenuBox()
- TextField()
- ExposedDropdownMenu()
- DropdownMenuItem()
The ExposedDropdownMenuBox() contains TextField() and ExposedDropdownMenu(). In the menu, we add items using the DropdownMenuItem().
ExposedDropdownMenuBox():
@Composable
fun ExposedDropdownMenuBox(
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
content: @Composable ExposedDropdownMenuBoxScope.() -> Unit
)
expanded – Whether the menu is visible or not.
onExpandedChange – It is called when the exposed dropdown menu is clicked.
modifier – The Modifier to change the layout.
content – The content of this menu box. We add the text field and exposed menu here.
ExposedDropdownMenu():
@Composable
fun ExposedDropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
scrollState: ScrollState = rememberScrollState(),
content: @Composable ColumnScope.() -> Unit
)
expanded – Whether the menu is visible or not.
onDismissRequest – It is called when the user taps outside the menu’s bounds or presses the back button.
modifier – The Modifier to change the layout.
scrollState – Scroll state of the menu.
content – The content of the menu. We add items using the DropdownMenuItem() method.
Example:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyUI() {
val moviesList = listOf(
"Iron Man",
"Thor: Ragnarok",
"Captain America: Civil War",
"Doctor Strange",
"The Incredible Hulk",
"Ant-Man and the Wasp"
)
var expanded by remember { mutableStateOf(false) }
var selectedMovie by remember { mutableStateOf(moviesList[0]) }
// menu box
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
// textfield
TextField(
modifier = Modifier
.menuAnchor(), // menuAnchor modifier must be passed to the text field for correctness.
readOnly = true,
value = selectedMovie,
onValueChange = {},
label = { Text("Movies") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
// menu
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
},
) {
// menu items
moviesList.forEach { selectionOption ->
DropdownMenuItem(
text = { Text(selectionOption) },
onClick = {
selectedMovie = selectionOption
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}
Output:

We set the readOnly parameter of the TextField() to true. As a result, it doesn’t accept the input and displays the currently selected movie. Let’s make it editable and filter the menu items based on the input.
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MyUI() {
val moviesList = listOf(
"Iron Man",
"Thor: Ragnarok",
"Captain America: Civil War",
"Doctor Strange",
"The Incredible Hulk",
"Ant-Man and the Wasp"
)
var expanded by remember { mutableStateOf(false) }
var textFieldValue by remember { mutableStateOf("") }
// container for textfield and menu
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
// textfield
TextField(
modifier = Modifier
.menuAnchor(), // menuAnchor modifier must be passed to the text field for correctness
value = textFieldValue,
onValueChange = { newValue ->
textFieldValue = newValue
},
label = { Text("Movies") },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
// filter options based on text field value
val filteringOptions = moviesList.filter { it.contains(textFieldValue, ignoreCase = true) }
if (filteringOptions.isNotEmpty()) {
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
filteringOptions.forEach { selectedMovie_ ->
DropdownMenuItem(
text = { Text(selectedMovie_) },
onClick = {
textFieldValue = selectedMovie_
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}
}
Output:

Note: Instead of TextFiled, you can also use OutlinedTextField.
This is all about dropdown menu APIs in Material 3 Jetpack Compose. I hope you have learned something new. If you have any doubts, comment below.
Related Articles:
References: