Skip to content

OverlayDropdownMenu

OverlayDropdownMenu is a BasicComponent wrapper that opens an OverlayDropdownPopup when clicked. Unlike OverlayDropdownPreference, it does not own a single selection index — selection state lives entirely on each DropdownItem's selected and onClick. Use it for action menus, multi-select menus, or any case where the items in a popup do not share one mutually exclusive choice.

Prerequisite

This component depends on Scaffold providing COUIPopupHost to render popup content. It must be used within Scaffold, otherwise popup content will not render correctly.

Import

kotlin
import io.github.suqi8.coui.kmp.menu.OverlayDropdownMenu
import io.github.suqi8.coui.kmp.basic.DropdownEntry
import io.github.suqi8.coui.kmp.basic.DropdownItem

Basic Usage

Wrap a single DropdownEntry to render a basic dropdown menu row:

kotlin
var selectedIndex by remember { mutableStateOf(0) }
val entry = DropdownEntry(
    items = listOf("Option 1", "Option 2", "Option 3").mapIndexed { index, text ->
        DropdownItem(
            text = text,
            selected = selectedIndex == index,
            onClick = { selectedIndex = index },
        )
    }
)

Scaffold {
    OverlayDropdownMenu(
        title = "Dropdown Menu",
        entry = entry
    )
}

Grouped Menu

Pass a List<DropdownEntry> to render multiple groups separated by dividers. By default, collapseOnSelection is entries.size <= 1, so multi-group menus stay open after each selection.

kotlin
var sizeIndex by remember { mutableStateOf(0) }
var colorIndex by remember { mutableStateOf(0) }
val entries = listOf(
    DropdownEntry(
        items = listOf("Small", "Medium").mapIndexed { index, text ->
            DropdownItem(text = text, selected = sizeIndex == index, onClick = { sizeIndex = index })
        }
    ),
    DropdownEntry(
        items = listOf("Red", "Green", "Blue").mapIndexed { index, text ->
            DropdownItem(text = text, selected = colorIndex == index, onClick = { colorIndex = index })
        }
    )
)

Scaffold {
    OverlayDropdownMenu(
        title = "Grouped Menu",
        entries = entries,
        collapseOnSelection = false
    )
}

Multi Select

Selection state lives on DropdownItem.selected, so multiple items can be selected simultaneously by toggling each item's value from onClick.

kotlin
var selected by remember { mutableStateOf(setOf("A1", "B2")) }
val entries = listOf(
    DropdownEntry(
        items = listOf("A1", "A2").map { text ->
            DropdownItem(
                text = text,
                selected = text in selected,
                onClick = {
                    selected = if (text in selected) selected - text else selected + text
                }
            )
        }
    ),
    DropdownEntry(
        items = listOf("B1", "B2", "B3").map { text ->
            DropdownItem(
                text = text,
                selected = text in selected,
                onClick = {
                    selected = if (text in selected) selected - text else selected + text
                }
            )
        }
    )
)

Scaffold {
    OverlayDropdownMenu(
        title = "Multi Select Menu",
        entries = entries,
        collapseOnSelection = false
    )
}

Items with Icons and Summaries

Each DropdownItem can display a leading icon and a summary line below its text. The icon lambda receives a pre-sized Modifier that should be applied to the icon composable.

kotlin
val entry = DropdownEntry(
    items = listOf(
        DropdownItem(
            text = "Rename",
            summary = "Change the display name",
            icon = { modifier ->
                Icon(
                    modifier = modifier,
                    imageVector = COUIIcons.Rename,
                    contentDescription = null,
                )
            },
            onClick = { /* handle action */ },
        ),
        DropdownItem(text = "Delete", onClick = { /* handle action */ }),
    )
)

Scaffold {
    OverlayDropdownMenu(
        title = "Item Icons",
        entry = entry
    )
}

Observe Expanded State

kotlin
var expanded by remember { mutableStateOf(false) }
val entry = DropdownEntry(
    items = listOf("Option 1", "Option 2", "Option 3").map { DropdownItem(text = it) }
)

Scaffold {
    OverlayDropdownMenu(
        title = "Observe Expanded",
        summary = if (expanded) "Expanded" else "Collapsed",
        entry = entry,
        onExpandedChange = { expanded = it }
    )
}

Items with a Hint Slot

The hint slot renders between the title block and the selection indicator, capped at 40dp wide. It suits a red dot, a count badge, or a very short label. Matching ColorOS, the hint is hidden entirely while the row is disabled.

kotlin
val entry = DropdownEntry(
    items = listOf(
        DropdownItem(text = "Inbox", hint = { Badge(count = 12) }),
        DropdownItem(text = "Updates", hint = { Badge() }),
        // The badge is suppressed because the row is disabled.
        DropdownItem(text = "Archive", hint = { Badge(count = 3) }, enabled = false),
    )
)

Scaffold {
    OverlayDropdownMenu(title = "Hints", entry = entry)
}

Group Headers

A DropdownEntry can declare a title, rendered above its items as a non-clickable header row (12sp medium, secondary label color, at most 2 lines). Headers coexist with the group divider that already separates adjacent entries.

kotlin
val entries = listOf(
    DropdownEntry(
        title = "Sort by",
        items = listOf("Name", "Date modified").map { DropdownItem(text = it) }
    ),
    DropdownEntry(
        title = "Order",
        items = listOf("Ascending", "Descending").map { DropdownItem(text = it) }
    )
)

Scaffold {
    OverlayDropdownMenu(title = "Group Headers", entries = entries)
}

Alert Items

Set alert = true to mark a destructive action. Its title uses the error color instead of the normal label color; disabled alert rows still fall back to the disabled color.

kotlin
val entry = DropdownEntry(
    items = listOf(
        DropdownItem(text = "Rename"),
        DropdownItem(text = "Delete", alert = true),
    )
)

Scaffold {
    OverlayDropdownMenu(title = "Alert Item", entry = entry)
}

Component States

Disabled State

kotlin
OverlayDropdownMenu(
    title = "Disabled Menu",
    summary = "This menu is currently unavailable",
    entry = DropdownEntry(items = listOf(DropdownItem(text = "Option 1"))),
    enabled = false
)

The menu is also implicitly disabled when no DropdownEntry contains any items.

Disabled Items

Individual items can be disabled via DropdownItem.enabled, and an entire group via DropdownEntry.enabled. Disabled rows are grayed out and ignore clicks.

kotlin
val entries = listOf(
    DropdownEntry(
        items = listOf(
            DropdownItem(text = "Available option"),
            DropdownItem(text = "Unavailable option", enabled = false),
        )
    ),
    DropdownEntry(
        items = listOf(DropdownItem(text = "Whole group disabled")),
        enabled = false
    )
)

Scaffold {
    OverlayDropdownMenu(
        title = "Partially Disabled",
        entries = entries
    )
}

Properties

OverlayDropdownMenu Properties (Entries Overload)

Property NameTypeDescriptionDefault ValueRequired
entriesList<DropdownEntry>Dropdown entry groups separated by dividers-Yes
titleStringTitle of the menu row-Yes
modifierModifierModifier applied to the componentModifierNo
titleColorBasicComponentColorsTitle text color configurationBasicComponentDefaults.titleColor()No
summaryString?Summary description of the menunullNo
summaryColorBasicComponentColorsSummary text color configurationBasicComponentDefaults.summaryColor()No
dropdownColorsDropdownColorsColor configuration for dropdown itemsDropdownDefaults.dropdownColors()No
startAction@Composable (() -> Unit)?Custom start side contentnullNo
bottomAction@Composable (() -> Unit)?Custom bottom side contentnullNo
insideMarginPaddingValuesInternal content paddingBasicComponentDefaults.InsideMarginNo
maxHeightDp?Maximum height of the dropdown popupnullNo
enabledBooleanWhether component is interactivetrueNo
renderInRootScaffoldBooleanWhether to render the popup in the root (outermost) Scaffold. When true, the popup covers the full screen. When false, it renders within the current Scaffold's bounds with position compensationtrueNo
collapseOnSelectionBooleanWhether to close the popup after each selectionentries.size <= 1No
onExpandedChange((Boolean) -> Unit)?Callback when the expanded state changesnullNo

Entry Overload Properties

Property NameTypeDescriptionDefault ValueRequired
entryDropdownEntrySingle dropdown entry group-Yes
collapseOnSelectionBooleanWhether to close the popup after selectiontrueNo

All other parameters are identical to the entries overload above.

Property NameTypeDescriptionDefault ValueRequired
itemsList<DropdownItem>Items shown in this dropdown group-Yes
enabledBooleanWhether this group is enabled. False disables all items; true still respects each item's enabled statetrueNo
titleString?Optional non-clickable group header rendered above the items (12sp medium, secondary label, max 2 lines)nullNo
Property NameTypeDescriptionDefault ValueRequired
textStringText shown for the item-Yes
enabledBooleanWhether the item can be clicked. Disabled items are graytrueNo
selectedBooleanWhether the item is selectedfalseNo
onClick(() -> Unit)?Callback invoked when the item is clickednullNo
icon@Composable ((Modifier) -> Unit)?Icon shown before the item textnullNo
summaryString?Summary text shown below the item textnullNo
childrenList<DropdownItem>?Optional submenu items; cascading variants onlynullNo
hint@Composable (() -> Unit)?Optional trailing hint slot (badge, red dot, short count) shown before the selection indicator, width-capped at 40dp. Hidden entirely while the row is disablednullNo
alertBooleanWhether this is an alert (destructive) item; its title uses the error colorfalseNo
Property NameTypeDescription
contentColorColorColor of the option title
summaryColorColorColor of the option summary
containerColorColorBackground color of the option
selectedContentColorColorTitle color of the selected option
selectedSummaryColorColorSummary color of the selected option
selectedContainerColorColorBackground color of the selected option
selectedIndicatorColorColorColor of the selected indicator icon
disabledContentColorColorTitle color of a disabled option
alertContentColorColorTitle color of an alert option
headerColorColorTitle color of a group header row

Changelog

Released under the Apache-2.0 License