Compose Desktop, production-ready
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.
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.
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.
PdfReader(state)dev.nucleusframework:pdfiumWebView(state)dev.nucleusframework:composewebviewSpell 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.
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.
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.
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.
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.
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.
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.
nucleus.graalvm-runtimePeak 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.
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.
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.
1// SwiftUI app (2025) — still goes through UserNotifications + UNNotificationCenter2import SwiftUI3import UserNotifications45@main6struct MyApp: App {7 @UIApplicationDelegateAdaptor(NotifDelegate.self) var delegate8 var body: some Scene { WindowGroup { ContentView() } }9}1011func postBuildNotif() async {12 let center = UNUserNotificationCenter.current()1314 // 1. Authorization15 let ok = try? await center.requestAuthorization(options: [.alert, .sound])16 guard ok == true else { return }1718 // 2. Declare the actions and register the category up-front19 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])2526 let cat = UNNotificationCategory(identifier: "BUILD_DONE",27 actions: [reveal, copy, share], intentIdentifiers: [],28 options: .customDismissAction)29 center.setNotificationCategories([cat])3031 // 3. Build content32 let content = UNMutableNotificationContent()33 content.title = "Build complete"34 content.body = "Installer ready. What now?"35 content.categoryIdentifier = "BUILD_DONE"36 content.sound = .default3738 let req = UNNotificationRequest(identifier: UUID().uuidString,39 content: content, trigger: nil)40 try? await center.add(req)41}4243// 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 = self49 return true50 }5152 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: break62 }63 done()64 }65}66// + Info.plist NSUserNotificationsUsageDescription67// + handle authorization revoked mid-app-lifecycle1import dev.nucleusframework.notification.notify2import dev.nucleusframework.notification.NotificationAction34notify(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)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.
git push tag v1.0.0setup-nucleusJBR 25 or Liberica NIK, Gradle cache, Node, Linux packaging tools — one step, every runner.
setup-macos-signingTemporary keychain, .p12 imported from secrets, identities exposed to downstream steps.
build-macos-universallipo merge arm64 + x64, inside-out re-sign, notarize via notarytool, staple — one DMG out.
build-windows-appxbundleMerge amd64 + arm64 .appx into a .msixbundle, sign with SignTool — Microsoft Store ready.
generate-update-ymlSHA-512 every installer, emit latest-*.yml plus electron-builder block maps so clients download only changed blocks.
publish-releasegh release create with the right assets, marks -alpha / -beta / -rc as pre-release automatically.
Questions, a stuck migration, or just lurking. #nucleus is on the official Kotlin Slack.