Jetpack Compose:DropdownMenu ignores anchor and shows at bottom of screen in Compose Dialog

19 hours ago 1
ARTICLE AD BOX

Yeah this is a known issue with DropdownMenu inside Dialog. The problem is Dialog creates its own window, and DropdownMenu uses Popup internally which calculates position relative to that window's origin (0,0) instead of your actual anchor. So your Box wrapping doesn't help because the coordinates get lost.

I dealt with this same thing and found two approaches that work:


Option 1: Use AlertDialog instead

AlertDialog handles the window differently and dropdown anchoring just works inside it.

@Composable fun CustomComposeDialog(onDismiss: () -> Unit) { var textInput1 by remember { mutableStateOf("") } var textInput2 by remember { mutableStateOf("") } var headerMenuExpanded by remember { mutableStateOf(false) } val textFieldMenuExpanded = textInput1.length > 3 AlertDialog( onDismissRequest = onDismiss, confirmButton = { TextButton(onClick = onDismiss) { Text("Close") } }, title = { Text( text = "Custom Entry", style = MaterialTheme.typography.headlineSmall ) }, text = { Column( verticalArrangement = Arrangement.spacedBy(16.dp) ) { Box { Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Text("Category Options") IconButton(onClick = { headerMenuExpanded = true }) { Icon(Icons.Default.ArrowDropDown, contentDescription = null) } } DropdownMenu( expanded = headerMenuExpanded, onDismissRequest = { headerMenuExpanded = false } ) { DropdownMenuItem( text = { Text("Option A") }, onClick = { headerMenuExpanded = false } ) DropdownMenuItem( text = { Text("Option B") }, onClick = { headerMenuExpanded = false } ) } } Box { OutlinedTextField( value = textInput1, onValueChange = { textInput1 = it }, label = { Text("Search or Type...") }, modifier = Modifier.fillMaxWidth() ) DropdownMenu( expanded = textFieldMenuExpanded, onDismissRequest = { }, properties = PopupProperties(focusable = false) ) { listOf("Result 1", "Result 2", "Result 3").forEach { suggestion -> DropdownMenuItem( text = { Text(suggestion) }, onClick = { textInput1 = suggestion } ) } } } OutlinedTextField( value = textInput2, onValueChange = { textInput2 = it }, label = { Text("Additional Notes") }, modifier = Modifier.fillMaxWidth() ) } } ) }

This works but you lose some layout control since AlertDialog has its own structure.


Option 2: Keep Dialog but use ExposedDropdownMenuBox

This is what I actually ended up using. ExposedDropdownMenuBox handles its own anchoring internally so it works fine inside Dialog.

@OptIn(ExperimentalMaterial3Api::class) @Composable fun CustomComposeDialog(onDismiss: () -> Unit) { var textInput1 by remember { mutableStateOf("") } var textInput2 by remember { mutableStateOf("") } var headerMenuExpanded by remember { mutableStateOf(false) } var textFieldMenuExpanded by remember { mutableStateOf(false) } Dialog(onDismissRequest = onDismiss) { Surface( shape = RoundedCornerShape(16.dp), color = MaterialTheme.colorScheme.surface, modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Column( modifier = Modifier.padding(20.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text( text = "Custom Entry", style = MaterialTheme.typography.headlineSmall ) // Header dropdown ExposedDropdownMenuBox( expanded = headerMenuExpanded, onExpandedChange = { headerMenuExpanded = it } ) { Row( modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryNotEditable) .clickable { headerMenuExpanded = true }, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Text("Category Options") Icon(Icons.Default.ArrowDropDown, contentDescription = null) } ExposedDropdownMenu( expanded = headerMenuExpanded, onDismissRequest = { headerMenuExpanded = false } ) { DropdownMenuItem( text = { Text("Option A") }, onClick = { headerMenuExpanded = false } ) DropdownMenuItem( text = { Text("Option B") }, onClick = { headerMenuExpanded = false } ) } } // TextField with suggestions ExposedDropdownMenuBox( expanded = textFieldMenuExpanded, onExpandedChange = { textFieldMenuExpanded = it } ) { OutlinedTextField( value = textInput1, onValueChange = { textInput1 = it textFieldMenuExpanded = it.length > 3 }, label = { Text("Search or Type...") }, modifier = Modifier .fillMaxWidth() .menuAnchor(MenuAnchorType.PrimaryEditable) ) if (textInput1.length > 3) { ExposedDropdownMenu( expanded = textFieldMenuExpanded, onDismissRequest = { textFieldMenuExpanded = false } ) { listOf("Result 1", "Result 2", "Result 3").forEach { suggestion -> DropdownMenuItem( text = { Text(suggestion) }, onClick = { textInput1 = suggestion textFieldMenuExpanded = false } ) } } } } OutlinedTextField( value = textInput2, onValueChange = { textInput2 = it }, label = { Text("Additional Notes") }, modifier = Modifier.fillMaxWidth() ) TextButton( onClick = onDismiss, modifier = Modifier.align(Alignment.End) ) { Text("Close") } } } } }

The important bits:

ExposedDropdownMenuBox manages anchoring with the menuAnchor() modifier so it works even inside a Dialog window

Use MenuAnchorType.PrimaryNotEditable for the header row since it's not a text field

Use MenuAnchorType.PrimaryEditable for the text field so user can keep typing while menu is open

Control expanded state in onValueChange based on your length > 3 logic

Read Entire Article