Skip to main content
Duet and Compose Multiplatform are the closest pair on this site’s comparison pages: both are Kotlin Multiplatform, both compile shared Kotlin for iOS with no bundled runtime, and on Android both render with Jetpack Compose. They differ on exactly one decision — Compose Multiplatform shares the UI too, rendering Compose on iOS from the same code, while Duet, Modaal’s cross-platform parity framework for native iOS and Android apps, keeps the iOS interface in SwiftUI and shares the logic only. Everything else on this page follows from that one decision. Last reviewed: September 1, 2026. Compose Multiplatform and Kotlin claims were checked against JetBrains’ official documentation on this date.

How do Duet and Compose Multiplatform differ?

On Android the two stacks converge fully: JetBrains’ own documentation states that on Android a Compose Multiplatform app “simply runs on Jetpack Compose”, and Duet’s emitted Android app pins Jetpack Compose through the androidx BOM (2025.06.01 in the current catalog). There is no Compose Multiplatform dependency in a Duet project — the org.jetbrains.compose artifacts exist for the non-Android targets Duet does not render on.

What does Compose Multiplatform’s iOS rendering actually do?

Compose Multiplatform has been stable on iOS since 1.8 (May 2025); current is 1.12.0 (August 2026). On iOS the framework draws components onto a canvas via Skia — the same rendering-ownership decision Flutter makes, but without a language runtime: the Kotlin is compiled by Kotlin/Native, so the quadrant cell is “framework draws, compiled logic.” Two qualifiers keep this description current:
  • Text input is going hybrid. Since 1.11.0 an experimental text field is backed by a real native UIView, giving platform-native gestures, selection handles and context menus inside the canvas.
  • Accessibility is bridged to the native services. Compose semantics are mapped to native iOS Accessibility Services — VoiceOver and Full Keyboard Access work, and testTag surfaces as accessibilityIdentifier for XCTest audits. The documented shape is re-implementation plus mapping: the framework maintains the bridge, and platform behaviors arrive through it rather than by default. A fully native interface inherits new OS accessibility features on the OS’s schedule.
Interop runs both directions — ComposeUIViewController embeds Compose screens in SwiftUI apps, and UIKitViewController embeds native views inside Compose — so mixed adoptions are a documented path. JetBrains’ integration guide names maps, camera and web views as the cases for a native view inside Compose, and the same interop is the customization path: the catalog is Material 3 on every platform, so a control that must look and behave like UIKit’s own is a native view embedded through the interop — built in Kotlin through Kotlin/Native’s Objective-C interop, or in Swift when the API is Swift-only — and anything outside the canvas — a home-screen widget, a share extension — is Swift regardless. The fit is UI-neutral business applications whose screens are the same everywhere; the more the iOS interface must differ from the Android one, the more of it is written natively with the framework in between.

If the language is the same, what is the actual choice?

Fidelity versus coverage, and what “agree” means:
  • Compose Multiplatform maximizes shared code: interface included, one implementation, agreement by construction. The iOS interface is Compose — Material by default, themable toward Cupertino idiom, but authored in the shared dialect and rendered by the framework.
  • Duet fixes the interface as each platform’s own and narrows sharing to what a fixture can check: state, actions, effects and their ordering. The iOS app is SwiftUI a platform engineer (or Apple’s own tooling) recognizes without caveats; the price is a second view layer, written per feature. Agreement is by verification — recorded fixtures replayed on both platforms, byte-compared in CI.
Stated plainly, as a position and not a measurement: Modaal’s preference for native fidelity is a product judgment about what mobile interfaces should be. Compose Multiplatform’s bet — that one excellent interface implementation beats two idiomatic ones — is a coherent judgment in the other direction, and JetBrains’ a11y and text-input work above is that bet being taken seriously.

What if one screen must be native and fast?

On Android nothing changes: Compose Multiplatform is Jetpack Compose there, and a camera preview, a SurfaceView or a vendor SDK view is the same AndroidView interop a Duet Android app would use. On iOS, drawing-heavy screens stay shared — Compose’s Canvas renders through Skia in Kotlin — while an island that must be UIKit’s own (the docs’ own examples: MKMapView, a camera, WKWebView) is a UIKitView whose factory returns the native view, hosted inside a Skia-drawn screen. Capture and processing pipelines — AVFoundation on iOS, CameraX and MediaCodec on Android — are platform code behind expect/actual declarations or a Swift wrapper the Kotlin calls into. The drawbacks: the iOS island is UIKit code inside a framework-rendered screen, so that screen has two renderers and two ownership models, and the pipeline’s logic sits outside the shared implementation, kept equal by review. On Android there is no drawback relative to Duet, because the toolkit is the same. Do you write and compile native code for it? On Android, no new language: the island is Kotlin in the same Gradle build. On iOS, usually not Swift either. Kotlin/Native’s Objective-C interop lets Kotlin construct UIKit objects directly — the docs’ own factory builds MKMapView() in Kotlin — and Xcode compiles the app target around the framework Kotlin/Native produces. Swift becomes necessary for Swift-only APIs, because “pure Swift modules are not yet supported” by the interop: a SwiftUI view such as Map, or a Swift-only vendor SDK, is written in Swift, exposed to Objective-C, and compiled by Xcode. Can the island be SwiftUI or Compose? Compose is the interface on Android already. On iOS a SwiftUI view is the documented case of the Swift exception above: the SwiftUI integration guide wraps it in UIHostingController, hands the controller to UIKitViewController, and states that “you have to write them in Swift and pass them to a Kotlin function”. The reverse direction, ComposeUIViewController inside a SwiftUI app, is documented on the same page. In a Duet app the island is ordinary platform code — AVFoundation and Metal in the SwiftUI app, CameraX and MediaCodec in the Compose app — with nothing between it and the OS. The reducer decides when capture starts, which filter applies and what happens with the result; a worker runs the pipeline. What is recorded and byte-compared is that seam: the requests the reducer makes and the results that come back. The pipeline’s internals are outside the verified surface, and the island is written twice, once per platform: Swift compiled by Xcode, Kotlin compiled by Gradle, by design.

What does Compose Multiplatform do better?

  • More shared code. The interface is written once. Duet writes it twice by design.
  • The iteration loop. Compose Hot Reload is stable and on by default since 1.10.0 (January 2026); 1.12.0 added an experimental MCP server so AI agents can drive reloads, take screenshots and inspect the semantic tree.
  • Reach beyond the two phone platforms. Desktop is stable and the web target is in beta from the same codebase; Duet targets iOS and Android only.
  • One design system by default. If the product wants the same branded look everywhere, sharing the interface is the shorter path to it.

When should you pick which?

Pick Compose Multiplatform for UI-neutral business applications — forms, lists, dashboards — when your team is Kotlin-first, a single branded interface across platforms (and possibly desktop and web) is a feature, and platform-idiomatic iOS is a nice-to-have rather than a requirement. Pick Duet when SwiftUI-native iOS is a requirement, when you want no cross-platform UI dependency in the iOS app, or when the domain needs behavior agreement to be a mechanical check — the recorded corpus is what turns “the apps agree” from a review judgment into a failing build. The two stacks also compose over time in one direction: because Duet’s core is ordinary Kotlin Multiplatform, its skills, libraries and tooling transfer — kotlinx, Ktor, the current stable Kotlin (2.4.10, which is what the Duet scaffold emits). Kotlin’s Swift Export, in alpha since Kotlin 2.4.0, is the platform’s stated direction for the Swift-facing seam both stacks consume today through the Objective-C bridge.

Common questions

No. Duet’s shared core is plain Kotlin Multiplatform — logic only, no UI framework in commonMain. Android renders with Jetpack Compose (androidx artifacts), iOS with SwiftUI. Compose Multiplatform is JetBrains’ extension of Jetpack Compose to non-Android targets, and a Duet project has no dependency on it.
Technically the interop exists (a Compose screen is embeddable in SwiftUI via ComposeUIViewController), but a Duet project’s parity contract covers logic, and its iOS interface convention is SwiftUI. Mixing rendering models per screen re-creates the consistency problem the architecture is built to remove — if shared UI is what you want, Compose Multiplatform proper is the honest choice.
Yes — a defect in shared Kotlin ships to both apps, in either stack. The difference is the safety net around change: Duet’s recorded scenarios replay on both platforms in CI, so a behavior change (or regression) is visible as a fixture diff on the commit that caused it. Compose Multiplatform projects bring their own test strategy, typically shared unit tests in commonTest.
Both stacks stand on the same runtime facts: Kotlin/Native compiles to machine code and manages memory with a concurrent mark-and-sweep collector over a shared heap — a small runtime, no VM or interpreter. Whatever holds for one stack’s iOS core holds for the other’s; it is not a differentiator between them.
Google officially supports Kotlin Multiplatform for sharing business logic between Android and iOS, and ships KMP artifacts for Room, DataStore, ViewModel and more — that support statement covers the shared-core layer both stacks use. Compose Multiplatform itself is developed by JetBrains, extending Google’s Jetpack Compose beyond Android.
Duet projects are scaffolded by Modaalstarting a Duet project walks through the two template cards.

Sources and further reading

The best cross-platform frameworks in 2026

The full survey — where the share-the-UI and share-the-logic cells sit on the quadrant.

Handling platform-specific UI

How Duet keeps two native interfaces honest: the presentation contract and recorded divergence.

Should you share code between iOS and Android?

The decision one level up: write twice, share everything, or share the logic.

How to build testable Android apps

The recorded-scenario discipline that gives Duet its verification story, shown in code.
Last modified on September 9, 2026