Nested Dropdown Menus in Compose
Skip’s open-source SkipUI library implements the SwiftUI API for Android. To do so, SkipUI leverages Compose, Android’s own modern, declarative UI framework.
While implementing SwiftUI’s Menu, we discovered that Compose doesn’t build in support for nested dropdown menus. Googling revealed that we weren’t the only devs wondering how to present a sub-menu from a Compose menu item, but the answers we found didn’t meet our needs. The code below represents our own simple, general solution to nested dropdown menus in Compose.
Note: SkipUI’s implementation is tied to SwiftUI internals, so this is an untested and simplified port of the actual code.
// Simple menu model. You could expand this for icons, section
// headings, dividers, etc
class MenuModel(title: String, val items: List<MenuItem>): MenuItem(title, {})
open class MenuItem(val title: String, val action: () -> Unit)
// Display a menu model, whose items can include nested menu models
@Composable fun DropdownMenu(menu: MenuModel) {
val isMenuExpanded = remember { mutableStateOf(false) }
val nestedMenu = remember { mutableStateOf<MenuModel?>(null) }
val coroutineScope = rememberCoroutineScope()
// Action to replace the currently-displayed menu with the
// given one on item selection. The hardcoded delays are
// unfortunate but improve the user experience
val replaceMenu: (MenuModel?) -> Unit = { menu ->
coroutineScope.launch {
// Allow selection animation before dismiss
delay(200)
isMenuExpanded.value = false
// Prevent flash of primary menu on nested dismiss
delay(100)
nestedMenu.value = null
if (menu != null) {
nestedMenu.value = menu
isMenuExpanded.value = true
}
}
}
DropdownMenu(expanded = isMenuExpanded.value, onDismissRequest = {
isMenuExpanded.value = false
coroutineScope.launch {
// Prevent flash of primary menu on nested dismiss
delay(100)
nestedMenu.value = null
}
}) {
for (item in menu.items) {
DropdownMenuItem(text = { Text(item.title) }, onClick = {
item.action()
replaceMenu(item as? MenuModel)
})
}
}
}
You can see this in action in the Skip Showcase app’s menu playground:

We hope that you find this useful! If you have questions or suggestions for improvements, please reach out to us on Mastodon @skiptools@mas.to, via chat skiptools.slack.com, or in our discussion forums.