Nucleus
Window & toolkits

Context menus

Replace Compose-drawn context menus with the OS-looking menu — NSMenu on macOS, a Fluent flyout on Windows, and Adwaita or Breeze on Linux.

By default, Compose Desktop draws its own context menus, which do not match the desktop they run on. Nucleus can replace them with the platform menu: a real NSMenu on macOS, and a Compose flyout styled as Fluent on Windows, Adwaita on GTK desktops, or Breeze on Qt desktops. This page covers the opt-in, the item types, and how items reach the menu.

Turn on the native menu

Set nativeContextMenu on the window:

import dev.nucleusframework.application.DecoratedWindow
import dev.nucleusframework.application.nucleusApplication

fun main() = nucleusApplication {
    DecoratedWindow(
        onCloseRequest = ::exitApplication,
        title = "MyApp",
        nativeContextMenu = true,
    ) {
        // Text fields now show the OS-looking Cut/Copy/Paste menu
    }
}

This covers ContextMenuArea, the text-field Cut/Copy/Paste menu, and spellcheck suggestions. The parameter is also available on HostedWindow.

The native menu is a Tao backend feature. On the AWT backends the parameter is accepted and ignored, so the same code keeps Compose menus there.

You do not choose a style. The menu follows the platform, and on Linux it follows the session's toolkit — Breeze on Qt desktops, Adwaita everywhere else.

Add your own items

Build items with NucleusContextMenuItem, which extends Compose's ContextMenuItem with an icon and a shortcut label:

import androidx.compose.foundation.ContextMenuArea
import androidx.compose.foundation.ContextMenuItem
import dev.nucleusframework.application.contextmenu.ContextMenuIcon
import dev.nucleusframework.application.contextmenu.NucleusContextMenuDivider
import dev.nucleusframework.application.contextmenu.NucleusContextMenuItem

ContextMenuArea(
    items = {
        listOf(
            NucleusContextMenuItem(
                label = "Reveal in Finder",
                icon = ContextMenuIcon.Folder,
                onClick = { revealInFileManager(path) },
            ),
            NucleusContextMenuDivider,
            NucleusContextMenuItem(
                label = "Delete",
                icon = ContextMenuIcon.Delete,
                shortcut = "Del",
                onClick = { delete(path) },
            ),
        )
    },
) {
    FileRow(path)
}

NucleusContextMenuDivider is the separator, and NucleusContextMenuSubmenu nests a group. Its items lambda is evaluated when the menu opens, so a submenu can list something that changes:

import dev.nucleusframework.application.contextmenu.NucleusContextMenuSubmenu

NucleusContextMenuSubmenu(label = "Open recent") { recentFiles.map { fileItem(it) } }

Show keyboard shortcuts

An item with a stock icon gets the platform shortcut label for free — ⌘C on macOS, Ctrl+C elsewhere:

NucleusContextMenuItem(label = "Copy", icon = ContextMenuIcon.Copy, onClick = ::copy)

Pass shortcut to override the label, or shortcut = "" to suppress the stock one. ContextMenuIcon.stockShortcut() returns the label used for a given icon.

Shortcut labels are drawn by the Windows and Linux flyouts. macOS NSMenu does not render this field yet.

Detect the Linux toolkit

The Linux menu style is picked from the session, which you can also read yourself — for example to match an unrelated widget to the desktop:

import dev.nucleusframework.core.runtime.LinuxUiToolkit

when (LinuxUiToolkit.Current) {
    LinuxUiToolkit.Qt -> // KDE Plasma, LXQt, Deepin, …
    LinuxUiToolkit.Gtk -> // GNOME, Xfce, Cinnamon, MATE, …
}

LinuxUiToolkit.Current reads XDG_CURRENT_DESKTOP, DESKTOP_SESSION, and XDG_SESSION_DESKTOP, then falls back to KDE_FULL_SESSION and QT_QPA_PLATFORMTHEME. Unknown desktops and bare tiling window managers resolve to Gtk.

How it works

Turning on nativeContextMenu installs two Compose representations for the window subtree: NativeContextMenuRepresentation for ContextMenuArea, and NativeTextContextMenu for text fields. Both walk the ContextMenuItem list Compose hands them and convert it to a ContextMenuEntry tree through the ContextMenuItemInterpreter in scope.

That tree is then rendered per platform. macOS builds a real NSMenu through menu-macos and pops it up; Windows and Linux draw a Compose flyout at OS density, so the menu keeps system scale even inside a subtree that overrides LocalDensity.

Items arrive from independent contributors — the field's own Cut/Copy/Paste, your extras, the spellcheck suggestions — so the entry list is normalized before it is shown: leading, trailing, and consecutive separators are dropped, recursively into submenus. An empty list closes the menu instead of flashing an empty frame.

API reference

Package dev.nucleusframework.application.contextmenu, shipped in nucleus-application.

Build items

SymbolDescription
NucleusContextMenuItem(label, enabled, icon, shortcut, onClick)A ContextMenuItem with an optional icon and shortcut label.
NucleusContextMenuDividerSeparator item.
NucleusContextMenuSubmenu(label) { items }Nested group; items is evaluated when the menu opens.
ContextMenuIconCut, Copy, Paste, SelectAll, Delete, Folder, SfSymbol(name).
ContextMenuIcon.stockShortcut()Platform shortcut label for the editing icons, else null.

Control the menu

SymbolDescription
NativeContextMenuProvider(enabled) { }Installs the native menu for a subtree, without going through the window parameter.
isNativeContextMenuSupportedWhether this platform can show it.
LocalNativeContextMenuWhether the subtree currently uses the native menu.
LocalContextMenuDividerThe separator the active renderer draws, or null. Read it when you assemble item lists.
NativeContextMenuRepresentation / NativeTextContextMenuThe representations installed by the provider.

Interpret items

SymbolDescription
ContextMenuEntryItem(label, enabled, icon, onClick, shortcut), Separator, Submenu(label, items).
ContextMenuItemInterpreterMaps a Compose ContextMenuItem to a ContextMenuEntry.
DefaultContextMenuItemInterpreterThe default mapping.
LocalContextMenuItemInterpreterOverride the mapping for a subtree.

macOS popup primitives

Package dev.nucleusframework.menu.macos, shipped in menu-macos.

SymbolDescription
NativePopupMenuItemEntry(title, enabled, icon, onClick), Separator, Submenu(title, items).
isNativePopupMenuAvailableWhether the native bridge loaded.
popUpNativeMenu(items)Shows an NSMenu at the cursor.

Call popUpNativeMenu off the Tao UI thread. AppKit runs a nested tracking loop while the menu is open, and inside a Tao event callback that loop starves the event loop — animations, Dispatchers.Main, and delay all stop until the menu closes.

Notes

  • Icons render differently per platform: Breeze draws 16 dp vectors, Fluent uses Segoe Fluent Icons glyphs, and Adwaita follows the GTK convention of no icons in context menus. ContextMenuIcon.SfSymbol applies to macOS only.
  • The flyout closes when the owning window loses focus. Outside-click monitoring only sees this process, so focus is the one signal every backend reports — and it also covers Alt+Tab and clicks on the taskbar.
  • On macOS the menu needs the menu-macos native library; if it fails to load, isNativeContextMenuSupported is false and Compose menus are used.

What's next