
In this article, we will learn how to implement dropdown menu APIs in Jetpack Compose.
Prerequisites:
- Jetpack Compose Modifier
- Arrangement and Alignment
- Row, Column, and Box Layouts
- Text in Jetpack Compose
What is a Dropdown Menu in Android?
A dropdown menu displays multiple choices. Users can tap on any one of them. We display the menu when the user interacts with a UI element (like an icon or button) or performs a specific action.
Jetpack Compose provides 2 dropdown menu APIs:
1. Dropdown Menu:
This is a normal menu.
Example:

There are two elements – the icon button (3 vertical dots) and the menu. We place them in a box layout so that the menu is displayed on the screen properly.
Here is the complete code:
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyUI()
}
}
}
}
}
}
@Composable
fun MyUI() {
val listItems = arrayOf("Favorites", "Options", "Settings", "Share")
val disabledItem = 1
val contextForToast = LocalContext.current.applicationContext
// state of the menu
var expanded by remember {
mutableStateOf(false)
}
Box(
contentAlignment = Alignment.Center
) {
// 3 vertical dots icon
IconButton(onClick = {
expanded = true
}) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "Open Options"
)
}
// drop down menu
DropdownMenu(
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
// adding items
listItems.forEachIndexed { itemIndex, itemValue ->
DropdownMenuItem(
onClick = {
Toast.makeText(contextForToast, itemValue, Toast.LENGTH_SHORT)
.show()
expanded = false
},
enabled = (itemIndex != disabledItem)
) {
Text(text = itemValue)
}
}
}
}
}
Run it and tap on the icon button. You will see the menu.

Let’s understand the code.
First, we created 4 variables:
listItems – It is the list of items displayed on the menu.
disabledItem – The second item (Options) is disabled on the menu. We are storing its index value (1) here.
contextForToast – It is the context object for the toast message. We display toast when we tap on the item. In Jetpack Compose, we can get the context from LocalContext.current object.
expanded – It is the state of the menu. If it is true, the menu will be displayed. Otherwise, the menu will be removed from the screen.
Next, we have added a Box. We have put the IconButton and the DropdownMenu in it. The menu API looks like this:
@Composable
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit
)
expanded – Whether the menu is currently open and visible to the user. We passed our expanded object to it.
onDismissRequest – Called when the user requests to dismiss the menu, such as by tapping on the screen outside the menu’s bounds or when the back key is pressed. We set the expanded false so that the menu will be dismissed.
modifier – The modifier.
offset – It is used to control the position of the menu on the screen.
properties – Used to customize the behavior of the menu.
content – This is the content of the menu. We have added the items using the DropdownMenuItem() composable. The content block is a scrollable Column. So, if you use LazyColumn as the root layout, you will end up getting an error.
DropdownMenuItem() looks like this:
@Composable
fun DropdownMenuItem(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
content: @Composable RowScope.() -> Unit
)
onClick – Called when the item was clicked.
enabled – Whether the item is enabled. If it is false, the item will not be clickable, and onClick will not be invoked.
contentPadding – The padding applied to the content of this menu item.
interactionSource – The MutableInteractionSource.
content – The content of the menu item. It is a row scope. So, if you add multiple composables, they are placed horizontally.
Since our listItems is of type Array, we are using the Kotlin’s forEachIndexed() to place them inside the menu.
Dropdown Menu Position:
In the above output, the menu is placed on the left side. If you don’t like it, we can change it using the offset parameter of the DropdownMenu(). It accepts the value of type DpOffset which can be obtained from the DpOffset() function.
DropdownMenu(
// previous parameters here
offset = DpOffset(x = (-66).dp, y = (-10).dp)
)
Play around with x and y values until you are happy.
Output:

You can find more about offset in this Canvas article.
2. Exposed Dropdown Menu:
It displays the currently selected item above the list. It is divided into two types – read-only and editable.
Read-only Exposed Dropdown Menu:
It just displays the menu. We cannot search for the menu items.
Example:

It contains 2 elements – TextField and menu. If we tap on the TextField, it shows the menu.
Here is the complete code:
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyUI()
}
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyUI() {
val listItems = arrayOf("Favorites", "Options", "Settings", "Share")
val contextForToast = LocalContext.current.applicationContext
// state of the menu
var expanded by remember {
mutableStateOf(false)
}
// remember the selected item
var selectedItem by remember {
mutableStateOf(listItems[0])
}
// box
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
// text field
TextField(
value = selectedItem,
onValueChange = {},
readOnly = true,
label = { Text(text = "Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
// menu
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
// this is a column scope
// all the items are added vertically
listItems.forEach { selectedOption ->
// menu item
DropdownMenuItem(onClick = {
selectedItem = selectedOption
Toast.makeText(contextForToast, selectedOption, Toast.LENGTH_SHORT).show()
expanded = false
}) {
Text(text = selectedOption)
}
}
}
}
}
Run it and you will see the following output:

The code is similar to the normal dropdown menu, but there are a few changes.
First, we created selectedItem variable. It is used to display the selected item in the TextField.
Next, instead of Box(), we are using ExposedDropdownMenuBox(). It takes 4 parameters:
@ExperimentalMaterialApi
@Composable
fun ExposedDropdownMenuBox(
expanded: Boolean,
onExpandedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
content: @Composable ExposedDropdownMenuBoxScope.() -> Unit
)
expanded – Whether the Dropdown Menu is expanded or not.
onExpandedChange – It is called when the user clicks on the box.
content – The content to be displayed inside the box. We have added TextField and ExposedDropdownMenu. Let’s look at the menu API:
@Composable
fun ExposedDropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit
)
expanded – Whether the menu is currently open and visible to the user.
onDismissRequest – Called when the user requests to dismiss the menu, such as by tapping on the screen outside the menu’s bounds or pressing the back button.
content – The content of the dropdown menu. We are adding the items using the DropdownMenuItem() composable.
Editable Exposed Dropdown Menu:
It accepts the input via TextField. It filters out the menu items based on the input.
Example:

The code is similar to the read-only dropdown menu. But, there are two differences. The TextField() is editable and we filter the menu items as the user enters the data.
Here is the code:
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
YourProjectNameTheme(darkTheme = false) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
MyUI()
}
}
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun MyUI() {
val contextForToast = LocalContext.current.applicationContext
val listItems = arrayOf("Favorites", "Options", "Settings", "Share")
// state of the menu
var expanded by remember {
mutableStateOf(false)
}
// remember the selected item
var selectedItem by remember {
mutableStateOf(listItems[0])
}
// box
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
value = selectedItem,
onValueChange = { selectedItem = it },
label = { Text(text = "Label") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
// filter options based on text field value
val filteringOptions =
listItems.filter { it.contains(selectedItem, ignoreCase = true) }
if (filteringOptions.isNotEmpty()) {
// menu
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
// this is a column scope
// all the items are added vertically
filteringOptions.forEach { selectionOption ->
// menu item
DropdownMenuItem(
onClick = {
selectedItem = selectionOption
Toast.makeText(contextForToast, selectedItem, Toast.LENGTH_SHORT).show()
expanded = false
}
) {
Text(text = selectionOption)
}
}
}
}
}
}
Run it and you will see the following output:

Instead of TextFiled, you can also use OutlinedTextField.
This is all about dropdown menu APIs in Jetpack Compose. I hope you have learned something new. If you have any doubts, comment below.
Related Articles:
- How to Request Permissions in Jetpack Compose?
- How to Check Internet Connection in Android Programmatically?
- 5 Awesome Loading Animations made with Jetpack Compose
- AppLovin and Meta Mediation Guide in Android
References:
Hi, can you send me to email with the project it’s confused to the files ):
Which one? reply to my email: [email protected]
It did not work when I tried running the code. Something wrong with line 77 as this invoked three error messages:
1: “No passed value for parameter ‘text’ ” (line 77)
2: “Type mismatch: inferred type is () -> Unit but mutableInteractionSource was expected” (line 77)
3: “@Composable invocations can only happen from the context of a @Composable function.
Hope you can help me figure out what is wrong 🙂
Can you share your code?
1) “Instead of TextFiled, you can also use OutlinedTextField.”, may be “Instead of TextField…
2) I try.. The examples not work.
If possible, I would like to see the solution. Thanks.
The examples are working. Can you share your code? either on stackoverflow or contact me @ [email protected]
Can you provide repo? I used MyUI composable from your snippet but having issues opening the keyboard.
I updated the post. Currently, I am using Jetpack Compose 1.3.1. You can find the gradle files here: https://semicolonspace.com/jetpack-compose-1-3-1-gradle-files/ If you still have the problem, send me an email at [email protected]
Sorry I meant repo for the code.
There is no repo.
I think it’s totally unhinged that there isn’t a basic built-in Dropdown List/Menu in Compose. Sure, just like for a TextField, you could create your own fancy one with a bunch of options and functionality and appearance, but I think it’s totally ridiculous you cannot create a BASIC UI element in a few lines of code. Nuttier than squirrel feces
When using material3 you need to set the TextField’s modifier to Modifier.menuAnchor(), See https://stackoverflow.com/questions/73946042/jetpack-compose-exposeddropdownmenu-not-showing-up-when-pressed for more details
Thanks for the tutorial!