A Compose Multiplatform library that ports the chat_context_menu Flutter package. It provides an iOS-style chat context menu with arrow-shaped popovers, modal barriers, and smart screen-edge positioning.
- Arrow popover menus — vertical and horizontal axes with configurable arrow size and corner shape
- Smart layout — automatically positions the menu above/below or left/right based on screen bounds and pointer position
- Modal barrier with hole cutout — dim everything except the anchor message when
excludeAnchorFromBarrier = true - Animated transitions — configurable duration with pivot at pointer/anchor
- Dismiss behavior — tap outside barrier, programmatic
hideMenu, optionalonClosecallback - 100% commonMain — single shared source set for all platforms
| Platform | Target |
|---|---|
| Android | minSdk 24 |
| iOS | Arm64, Simulator Arm64 |
| Desktop | JVM |
| Web | JS, Wasm |
Add to your gradle/libs.versions.toml:
[versions]
chatcontextmenu = "1.0.1"
[libraries]
chatcontextmenu = { module = "io.github.matkurban:chatcontextmenu", version.ref = "chatcontextmenu" }Then in build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.chatcontextmenu)
}
}
}Alternatively, add the dependency directly to your commonMain source set:
// build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.matkurban:chatcontextmenu:1.0.1")
}
}
}Import from io.github.matkurban.chatcontextmenu.ui.*.
Wrap your app in ChatContextMenuHost before using ChatContextMenuWrapper:
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import io.github.matkurban.chatcontextmenu.ui.ChatContextMenuHost
import io.github.matkurban.chatcontextmenu.ui.ChatContextMenuWrapper
import io.github.matkurban.chatcontextmenu.ui.holeShape
ChatContextMenuHost {
ChatContextMenuWrapper(
menuShape = RoundedCornerShape(10.dp),
menuModifier = Modifier
.background(MaterialTheme.colorScheme.surface, RoundedCornerShape(10.dp))
.padding(8.dp),
excludeAnchorFromBarrier = true,
barrierColor = Color.Black.copy(alpha = 0.26f),
barrierAnchorModifier = Modifier
.padding(4.dp)
.holeShape(RoundedCornerShape(8.dp)),
widgetContent = { showMenu, hideMenu ->
// bind showMenu to long-press / click on your anchor widget
},
menuContent = { hideMenu ->
// menu items; call hideMenu() when an action is selected
},
)
}| Composable | Description |
|---|---|
ChatContextMenuHost |
Root host that provides an overlay slot for context menus |
ChatContextMenuWrapper |
Wraps an anchor widget and shows the menu on demand |
ChatContextMenuVerticalWidget |
Pre-built vertical menu item layout |
ChatContextMenuHorizontalWidget |
Pre-built horizontal menu item layout |
| Parameter | Default | Description |
|---|---|---|
widgetContent |
— | Anchor UI; receives showMenu and hideMenu callbacks |
menuContent |
— | Menu panel content; receives hideMenu callback |
modifier |
Modifier |
Modifier for the anchor wrapper |
menuModifier |
Modifier |
Menu panel styling (background, padding, widthIn, shadow, etc.) |
menuShape |
RoundedCornerShape(8.dp) |
Corner shape for arrow path and layout |
barrierColor |
Color.Transparent |
Modal barrier color; transparent still dismisses when barrierDismissible = true |
arrowHeight |
8.dp |
Arrow height |
arrowWidth |
12.dp |
Arrow width |
spacing |
6.dp |
Gap between arrow and menu panel |
transitionDurationMillis |
150 |
Open/close animation duration |
onClose |
null |
Callback invoked after menu is fully dismissed |
horizontalMargin |
10.dp |
Horizontal margin from screen edge |
layoutMaxHeight |
null |
Optional max height for menu layout |
axis |
MenuAxis.Vertical |
Menu orientation (Vertical or Horizontal) |
topPadding |
56.dp |
Top safe-area padding for layout calculation |
excludeAnchorFromBarrier |
false |
Cut a hole in the barrier around the anchor |
barrierAnchorModifier |
Modifier |
Hole bounds around anchor; use Modifier.holeShape(Shape) for rounded cutout |
barrierDismissible |
true |
Whether tapping the barrier dismisses the menu |
| API | Description |
|---|---|
Modifier.holeShape(Shape) |
Marks a modifier chain with the shape used for barrier hole cutout |
This repository includes demo apps for each platform:
| Module | Description |
|---|---|
sample |
Shared demo UI (DemoApp) |
androidApp |
Android sample shell |
desktopApp |
JVM desktop app via Compose Desktop |
webApp |
Browser app (Wasm) |
Run the samples:
./gradlew :androidApp:installDebug
./gradlew :desktopApp:run
./gradlew :webApp:wasmJsBrowserDevelopmentRun- Kotlin 2.4.0+
- Compose Multiplatform 1.11.1+
- JDK 11+
library— published library (100%commonMain)sample— demo UI (not published)androidApp/desktopApp/webApp— sample apps
This project is licensed under the MIT License.