Compose Desktop, production-ready

Ship desktop with the Kotlin team you already have.

Compose Multiplatform draws the UI. Nucleus makes it a real desktop app — native chrome, OS APIs, packaging, and stores. Same Kotlin codebase as your Android, iOS and web apps. No second stack. No JNI circus.

Everywhere else, Kotlin owns the whole platform.
Now desktop does too.

On Android, Kotlin and Compose are first-class. On iOS and the web, the same story. Desktop had Compose for the UI — and left you to invent the rest: native chrome, OS APIs, signing, auto-update, stores. Vanilla Compose Desktop stops at the window. Nucleus is the production layer your Kotlin team was missing.

Android
OS APIsNative, built-in
Native UIMaterial 3 widgets
InputTouch · gestures
OptimizeR8 · ProGuard · AAB shrink
Package.aab via Gradle
DistributeGoogle Play, in two clicks
iOS
OS APIsKotlin/Native ↔ UIKit
Native UISwiftUI interop
InputTouch · Apple Pencil
OptimizeLLVM AOT · App Thinning
Package.ipa via Xcode
DistributeApp Store Connect
Web
Browser APIsKotlin/JS · Wasm
Native UIHTML / DOM interop
InputMouse · touch · pointer
OptimizeTree-shaking · code split
PackageWebpack bundle
DistributePush to any CDN
Desktop
OS APIsvia Nucleus40+ Kotlin modules · Native Access via Kotlin/Native
Native UIvia NucleusmacOS · Fluent · Yaru in Compose + native overlay
Inputvia NucleusMouse · keyboard · multi-touch · pen · Wayland gestures
Optimizevia NucleusGraalVM closed-world · JIT + AOT cache · native-lib stripping
Packagevia Nucleus18 formats, signed + notarized
Distributevia NucleusMS Store · App Store · Snap · GitHub · auto-update
Vanilla Compose Desktop
No touch / penLegacy trayNot production-ready
Skia window, mouse and keyboard only — no multi-touch, no pen. Tray APIs on Windows and Linux are legacy and barely usable; notifications stay minimal. System integration is a thin demo surface, not a production desktop stack. Six jpackage formats; you still own chrome, signing, auto-update, and stores.
Compose + Nucleus
Native chrome40+ OS modulesShip & stores
Same Compose UI — plus native decorated windows, 40+ Kotlin OS modules, 18 packaging formats, auto-update, and store-ready CI. Drop-in on the JetBrains plugin.
Batteries included

It ships components too — one Compose API, every platform.

Each one replaces four native engines you'd otherwise write, test, and keep in sync — behind a single composable identical on Android, iOS, web, and desktop.

PDF reader
PdfReader(state)dev.nucleusframework:pdfium
replacesPdfRendererPDFKitPDF.jsPDFBox
WebView
WebView(state)dev.nucleusframework:composewebview
replacesWebViewWKWebViewiframeWebView2
Runtime

40+ modules.
Every OS feature, first-class.

Spell check wires the OS engine into Compose text fields, and one flag swaps Compose menus for the platform's. Overlay flags cover watermarks and desktop widgets. TextureView and NativeView put GPU frames and platform widgets inside Compose. Plus the full OS surface — notifications, tray, hotkeys, dark mode — one Kotlin shape per capability.

New in 2.5
Spell check
The OS spell engine in Compose text fields — Hunspell, NSSpellChecker, Windows Spell Checking. Suggestions in the context menu.
New in 2.5
Native context menus
One flag swaps the Compose menu for the platform one — NSMenu, Fluent, Adwaita, Breeze, with shortcut labels.
New in 2.5
Overlay windows
Watermarks and desktop widgets — click-through, always-on-bottom, visible on every workspace.
NativeView
Embed NSView, HWND, or GtkWidget inside Compose — now with Compose blended over it in the same scene.
GPU render context
In-process GPU on the scene device — Skia DirectContext, Metal / ANGLE / EGL. No second GPU, no copy.
TextureView
External GPU textures in the Compose scene — D3D11, Metal, DMA-BUF. Real z-order, no CPU copy.
AwakeMode
Keep the system awake for long jobs without forcing the screen on — one handle per feature, no stepping on each other.
Differential updates
Download only the blocks that changed — electron-builder block maps, HTTP ranges, silent fallback to full.
Task Scheduler
WorkManager for desktop — periodic, cron, and on-boot tasks the OS runs even when the app is closed. launchd, Task Scheduler, systemd.
Decorated Window
Custom title bar, native controls, theme-aware. Tao backend by default.
Window Scaffold
Full-window layouts, glass regions, Mica/Acrylic, custom chrome primitives.
Native Notifications
macOS, Win Toast, freedesktop — one Kotlin API, plus per-platform DSL options.
System Tray
Status icons with menus, badges, click handlers — across all three OSes.
Dock & Launcher
Badges, jump lists, dock menus, Unity launcher entries.
Dark Mode Detector
Reactive OS theme — bridges Compose isSystemInDarkTheme under nucleusApplication.
Global Hotkey
OS-level shortcuts, multi-modifier, work app-wide — stable portal ids on Wayland.
Taskbar Progress
Progress bars on Windows taskbar, macOS dock, Unity launcher.
Deep Links
Protocol handlers + file associations on all platforms.
Native HTTP / SSL
OS trust store merged into JDK defaults.
Native Access
Write Kotlin/Native and call from JVM. No C, no glue.
Powered by TaoDefault backend

Tao is the Rust windowing crate behind Tauri 2. Nucleus uses it as the default window backend — no AWT — so you get TextureView for external GPU textures, NativeView for platform widgets, native Wayland, multi-touch, pen input, and window-scaffold chrome in the same Compose tree.

TextureView
External GPU textures composited in the scene — D3D11, Metal, DMA-BUF. No CPU frame copy.
NativeView
Embed NSView, HWND, or GtkWidget in Compose layout — same slot lifecycle as AndroidView.
Native Wayland
First-class Wayland support — no XWayland fallback, fractional scaling, gestures.
Multi-touch & pen
Pinch, swipe, rotate, pressure and tilt — every pointer event carries the full source.
Native accessibility

One semantics tree. Three native screen readers.

The Tao backend projects your Compose semantics onto NSAccessibility, UI Automation, and AT-SPI. VoiceOver, Narrator, and Orca just work — no extra wiring, no platform-specific code. And accessibility matters now more than ever: as AI agents increasingly interact with applications, they rely on the same structured accessibility trees to read and control programs.

Settings
AccountActive
VoiceOver
Focus moves to the switch
macOSAXIdentifier · testTag
VoiceOver·NSAccessibility
Ease of Access
Narrator
Describe the world around me
Focused element
WindowsAutomationId · testTag
Narrator·UI Automation
Accessibility
Screen Reader
OOrca Screen Reader
AT-SPI bus bridged via AccessKit
LinuxAccessKit author id · testTag
Orca·AT-SPI via AccessKit
No assistive tech attached? The backend skips the walk entirely, so scrolling and animation stay smooth. The first screen-reader query activates the projection automatically.
Five native looks

Match every desktop, by design.

Write your UI once in Compose, render it in the desktop style of your choice — macOS 26 Liquid Glass, Windows 11 Fluent, Ubuntu Yaru, IntelliJ Jewel, or cross-platform Material 3. macOS, Fluent and Yaru ship as open-source design systems on Maven Central (with Nucleus-backed decorated windows); Jewel is JetBrains' toolkit; Material 3 is the Nucleus fallback.

Settings
General
Appearance
Network
Dark mode
Liquid Glass
macOS Tahoecompose-macos-ui
Settings — Nucleus Demo
Appearance System
Dark mode
Follow system
Accent
Mica · Acrylic
Windows 11composefluent
Settings
Appearance
Dark style
Accent color
GTK · LibAdwaita
Ubuntu · GNOMEyarucompose
SettingsEditorPlugins
Appearance
Theme
Colors
Editor
Keymap
Theme
Darcula
Jewel
IntelliJ Platform
Cross-platform toolingdecorated-window-jewel
Settings
Dark mode
Follow system
Dynamic colorAccent
Material 3
Material You
Cross-platformdecorated-window-material3

Pick one toolkit per OS, or mix freely — every design system runs on every platform. You can ship a Fluent build on macOS while you prototype the native look. Either way, your DecoratedWindow stays the same Composable.

Two runtimes, one binary

Pick your tradeoff. Win either way.

Same Kotlin code, two runtimes: a GraalVM native image for instant cold start and a tiny resident set, or a modern JDK with AOT cache where HotSpot's JIT gets close to C++ and Rust on hot paths. Same source. Same build.

Closed world

GraalVM Native Image

Instant cold start. Tiny footprint.

Your whole app is AOT-compiled to a standalone binary. No JVM startup, no class loading — the process is alive in half a second. Smallest resident set on the market.

0.0s
Cold start
0MB
RAM idle
0MB
Binary
CPU throughputVery good · AOT compiled
C++
GraalVM PGO + Compose IR optimizations
Best for
CLIs & small appsSandboxed targetsApp Store / MSIXDistribution-first
nucleus.graalvm-runtime
Open world

JDK 25 + AOT Cache

Peak JIT throughput. Plugins, agents, full reflection.

HotSpot's C2 JIT is the most mature compiler ever built. JDK 25's AOT cache primes class metadata so you skip the warm-up — and you keep everything closed-world gives up: dynamic class loading, full reflection, JVM agents, scripting engines, live plugins and extensions.

0.0s
Cold start
0MB
RAM idle
0MB
Binary
CPU throughput≈ C++ / Rust on hot paths
C++
HotSpot C2 · escape analysis · vectorization
Best for
Plugin & extension hostsIDE-like toolsLong-running appsScripting & DSL runtimes
nucleus.aot-runtime

* Cold start and RAM measured on Windows 11 with a Hello World build. Binary size is the NSIS installer with maximum compression.

The Nucleus paradox

Native APIs.
Easier than native.

Win32 ITaskbarList3. NSUserNotifications. freedesktop D-Bus. IOKit. ScreenCaptureKit. Each desktop API is its own tiny ordeal — different language, different threading model, different conventions. Nucleus wraps every one in a Kotlin function that feels obvious. The result: a cross-platform abstraction simpler than the original, on every platform.

Native API · macOS · SwiftUI
1// SwiftUI app (2025) — still goes through UserNotifications + UNNotificationCenter
2import SwiftUI
3import UserNotifications
4
5@main
6struct MyApp: App {
7 @UIApplicationDelegateAdaptor(NotifDelegate.self) var delegate
8 var body: some Scene { WindowGroup { ContentView() } }
9}
10
11func postBuildNotif() async {
12 let center = UNUserNotificationCenter.current()
13
14 // 1. Authorization
15 let ok = try? await center.requestAuthorization(options: [.alert, .sound])
16 guard ok == true else { return }
17
18 // 2. Declare the actions and register the category up-front
19 let reveal = UNNotificationAction(identifier: "REVEAL",
20 title: "Reveal in Finder", options: [.foreground])
21 let copy = UNNotificationAction(identifier: "COPY",
22 title: "Copy path", options: [])
23 let share = UNNotificationAction(identifier: "SHARE",
24 title: "Share…", options: [.foreground])
25
26 let cat = UNNotificationCategory(identifier: "BUILD_DONE",
27 actions: [reveal, copy, share], intentIdentifiers: [],
28 options: .customDismissAction)
29 center.setNotificationCategories([cat])
30
31 // 3. Build content
32 let content = UNMutableNotificationContent()
33 content.title = "Build complete"
34 content.body = "Installer ready. What now?"
35 content.categoryIdentifier = "BUILD_DONE"
36 content.sound = .default
37
38 let req = UNNotificationRequest(identifier: UUID().uuidString,
39 content: content, trigger: nil)
40 try? await center.add(req)
41}
42
43// 4. Delegate to receive callbacks (must be retained, AppDelegate is easiest)
44class NotifDelegate: NSObject, UIApplicationDelegate,
45 UNUserNotificationCenterDelegate {
46 func application(_ a: UIApplication, didFinishLaunchingWithOptions o:
47 [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
48 UNUserNotificationCenter.current().delegate = self
49 return true
50 }
51
52 func userNotificationCenter(_ c: UNUserNotificationCenter,
53 didReceive r: UNNotificationResponse,
54 withCompletionHandler done: @escaping () -> Void) {
55 switch r.actionIdentifier {
56 case "REVEAL": revealInFinder(file)
57 case "COPY": copyPath(file)
58 case "SHARE": shareSheet(file)
59 case UNNotificationDismissActionIdentifier:
60 telemetry.log("notif_dismissed")
61 default: break
62 }
63 done()
64 }
65}
66// + Info.plist NSUserNotificationsUsageDescription
67// + handle authorization revoked mid-app-lifecycle
~74 linesThreading model: yours to figure out
One Kotlin API · all platforms
1import dev.nucleusframework.notification.notify
2import dev.nucleusframework.notification.NotificationAction
3
4notify(
5 title = "Build complete",
6 body = "Installer ready. What now?",
7 icon = Icon.AppIcon,
8 actions = listOf(
9 NotificationAction("reveal", "Reveal in Finder") {
10 Desktop.open(file.parent)
11 },
12 NotificationAction("copy", "Copy path") {
13 Clipboard.put(file.absolutePath)
14 },
15 NotificationAction("share", "Share…") { shareSheet(file) },
16 ),
17 onDismiss = { telemetry.log("notif_dismissed") },
18)
18 linesCoroutines-friendly · type-safe · zero callbacks lost
From tag to release

Push a tag. Get signed installers.

Nucleus ships reusable GitHub Actions that build, sign, notarize, bundle and publish for every desktop — without a copy-pasted YAML in sight. Six composite actions, one reference workflow, one tag push.

Trigger
git push tag v1.0.0
Matrix build
6 parallel runners
Ubuntu · amd64Ubuntu · arm64Windows · amd64Windows · arm64macOS · arm64macOS · x64
Sign · bundle · notarize
lipo universal · MSIX · staple
GitHub Release
installers + block maps + update YAML
Six composite actions
setup-nucleus

JBR 25 or Liberica NIK, Gradle cache, Node, Linux packaging tools — one step, every runner.

setup-macos-signing

Temporary keychain, .p12 imported from secrets, identities exposed to downstream steps.

build-macos-universal

lipo merge arm64 + x64, inside-out re-sign, notarize via notarytool, staple — one DMG out.

build-windows-appxbundle

Merge amd64 + arm64 .appx into a .msixbundle, sign with SignTool — Microsoft Store ready.

generate-update-yml

SHA-512 every installer, emit latest-*.yml plus electron-builder block maps so clients download only changed blocks.

publish-release

gh release create with the right assets, marks -alpha / -beta / -rc as pre-release automatically.

Talk with us

Questions, a stuck migration, or just lurking. #nucleus is on the official Kotlin Slack.