DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Context Menu in Compose — Long Press Menu, BottomSheet Actions & Selection Mode

Context Menu in Compose

Long-press interactions are crucial for rich UI experiences. Let's explore three patterns: custom dropdown menus, bottom sheets, and selection mode.

Pattern 1: Long Press Dropdown Menu

Use combinedClickable with LongClickLabel for accessible long-press with context actions:

var showMenu by remember { mutableStateOf(false) }
var selectedIndex by remember { mutableStateOf(-1) }

Box(
    modifier = Modifier
        .combinedClickable(
            onClick = { /* handle click */ },
            onLongClick = {
                selectedIndex = index
                showMenu = true
            },
            longClickLabel = "Show item actions"
        )
) {
    Text(item.title)

    DropdownMenu(
        expanded = showMenu && selectedIndex == index,
        onDismissRequest = { showMenu = false }
    ) {
        DropdownMenuItem(
            text = { Text("Edit") },
            onClick = {
                onEdit(item)
                showMenu = false
            }
        )
        Divider()
        DropdownMenuItem(
            text = { Text("Share") },
            onClick = {
                onShare(item)
                showMenu = false
            }
        )
        Divider()
        DropdownMenuItem(
            text = { Text("Delete") },
            onClick = {
                onDelete(item)
                showMenu = false
            }
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 2: Modal Bottom Sheet with Header

For mobile-friendly menus on large screens:

var showBottomSheet by remember { mutableStateOf(false) }

if (showBottomSheet) {
    ModalBottomSheet(onDismissRequest = { showBottomSheet = false }) {
        Column {
            // Header
            Text(
                "${item.title} — Actions",
                style = MaterialTheme.typography.titleMedium,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp),
                fontWeight = FontWeight.Bold
            )

            Divider()

            // Actions
            ListItem(
                headlineContent = { Text("Edit") },
                modifier = Modifier.clickable {
                    onEdit(item)
                    showBottomSheet = false
                }
            )
            ListItem(
                headlineContent = { Text("Share") },
                modifier = Modifier.clickable {
                    onShare(item)
                    showBottomSheet = false
                }
            )
            ListItem(
                headlineContent = { Text("Delete", color = Color.Red) },
                modifier = Modifier.clickable {
                    onDelete(item)
                    showBottomSheet = false
                }
            )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Pattern 3: Selection Mode with TopAppBar

Multi-select with toolbar:

var selectionMode by remember { mutableStateOf(false) }
val selectedItems = remember { mutableStateListOf<Item>() }

Scaffold(
    topBar = {
        if (selectionMode) {
            TopAppBar(
                title = { Text("${selectedItems.size} selected") },
                navigationIcon = {
                    IconButton(onClick = {
                        selectionMode = false
                        selectedItems.clear()
                    }) {
                        Icon(Icons.Default.Close, "Cancel")
                    }
                },
                actions = {
                    IconButton(onClick = { /* delete all */ }) {
                        Icon(Icons.Default.Delete, "Delete")
                    }
                },
                colors = TopAppBarDefaults.topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.primary
                )
            )
        } else {
            TopAppBar(title = { Text("Items") })
        }
    }
) { padding ->
    LazyColumn(contentPadding = padding) {
        items(itemList) { item ->
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .combinedClickable(
                        onClick = {
                            if (selectionMode) {
                                if (item in selectedItems) {
                                    selectedItems.remove(item)
                                } else {
                                    selectedItems.add(item)
                                }
                            }
                        },
                        onLongClick = {
                            if (!selectionMode) {
                                selectionMode = true
                                selectedItems.add(item)
                            }
                        }
                    )
                    .background(
                        if (item in selectedItems)
                            MaterialTheme.colorScheme.surfaceVariant
                        else
                            Color.Transparent
                    ),
                verticalAlignment = Alignment.CenterVertically
            ) {
                if (selectionMode) {
                    Checkbox(
                        checked = item in selectedItems,
                        onCheckedChange = {
                            if (it) selectedItems.add(item)
                            else selectedItems.remove(item)
                        },
                        modifier = Modifier.padding(8.dp)
                    )
                }
                Text(item.title, modifier = Modifier.weight(1f).padding(8.dp))
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Comparison Table

Pattern Use Case Mobile Desktop Accessibility
Dropdown Menu Single item, quick actions ✓✓ Good
Bottom Sheet Large action sets ✓✓ Excellent
Selection Mode Bulk operations ✓✓ Good

Ready to build Android apps faster? Download our 8 Android app templates.

Top comments (0)