> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modaal.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Duet vs Compose Multiplatform: Share Logic or Share UI?

> Both are Kotlin Multiplatform. Compose Multiplatform also shares the UI, drawn on a canvas on iOS; Duet keeps SwiftUI native and shares the logic only.

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?

<Frame>
  |                                          | Compose Multiplatform                                                                                                                                                                                                                                                                                                                                            | Duet                                                                                                                                  |
  | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
  | Who draws the screen on Android          | Jetpack Compose — the same androidx toolkit in both stacks                                                                                                                                                                                                                                                                                                       | Jetpack Compose                                                                                                                       |
  | Who draws the screen on iOS              | Compose, rendered by the framework onto a Skia canvas                                                                                                                                                                                                                                                                                                            | SwiftUI — Apple's own toolkit, authored natively                                                                                      |
  | Where feature logic lives                | Once, in Kotlin `commonMain`, compiled per platform                                                                                                                                                                                                                                                                                                              | Once, in Kotlin `commonMain`, compiled per platform                                                                                   |
  | What proves the two platforms agree      | One implementation — UI and logic alike                                                                                                                                                                                                                                                                                                                          | Recorded fixtures replayed on both platforms; CI compares byte-for-byte                                                               |
  | Shared fraction                          | Logic and UI                                                                                                                                                                                                                                                                                                                                                     | Logic, navigation decisions, effects and the test corpus; the two view layers are per platform                                        |
  | Customizing beyond the catalog on iOS    | Material 3 drawn by the framework; a control that must be UIKit's own is embedded through the interop, built in Kotlin via Objective-C interop or in Swift for Swift-only APIs                                                                                                                                                                                   | Every control is SwiftUI's own; customization is ordinary SwiftUI code                                                                |
  | A native, perf-heavy island              | Drawing-heavy screens stay shared (Compose `Canvas` on Skia); a camera, video or GPU island is `AndroidView` interop on Android and, on iOS, a `UIKitView` whose factory builds the UIKit view — in Kotlin through Objective-C interop, or in Swift for Swift-only APIs — hosted inside a Skia-drawn screen, with the pipeline's logic outside the shared Kotlin | Ordinary SwiftUI/UIKit and Compose/Android code, no bridge; the reducer decides, a worker runs the pipeline, and the seam is recorded |
  | Native code you write and compile for it | Usually Kotlin only: on Android the island is Kotlin in the same Gradle build; on iOS Kotlin/Native's Objective-C interop builds UIKit views from Kotlin, and Swift is needed only for Swift-only APIs, which Xcode compiles in the app target                                                                                                                   | Yes, by design: Swift compiled by Xcode, Kotlin compiled by Gradle                                                                    |
  | SwiftUI and Compose in the island        | Compose is the UI on Android; on iOS a SwiftUI view is wrapped in `UIHostingController` and passed to `UIKitViewController` — the docs say "you have to write them in Swift and pass them to a Kotlin function"                                                                                                                                                  | They are the interface: SwiftUI on iOS, Jetpack Compose on Android, by construction                                                   |
</Frame>

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](https://blog.jetbrains.com/kotlin/2025/05/compose-multiplatform-1-8-0-released-compose-multiplatform-for-ios-is-stable-and-production-ready/) (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](https://kotlinlang.org/docs/compose-ios-accessibility.html) — 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](https://kotlinlang.org/docs/multiplatform/compose-swiftui-integration.html) 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](/articles/duet#what-duet-requires-of-you). 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`](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/views-in-compose) 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`](https://kotlinlang.org/docs/multiplatform/compose-uikit-integration.html) 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](https://kotlinlang.org/docs/native-objc-interop.html) 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](https://kotlinlang.org/docs/multiplatform/compose-swiftui-integration.html) 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](/articles/duet-glossary#reducer) decides when capture starts, which filter applies and what happens with the result; a [worker](/articles/duet-glossary#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](https://blog.jetbrains.com/kotlin/2026/01/compose-multiplatform-1-10-0/) 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](/articles/testable-android-apps) 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

<AccordionGroup>
  <Accordion title="Is Duet built on Compose Multiplatform?" icon="diagram-project">
    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.
  </Accordion>

  <Accordion title="Can I mix the two — Compose Multiplatform screens inside a Duet app?" icon="layer-group">
    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.
  </Accordion>

  <Accordion title="Does shared logic mean shared bugs?" icon="bug">
    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.
  </Accordion>

  <Accordion title="What about Kotlin/Native performance and the GC on iOS?" icon="gauge">
    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.
  </Accordion>

  <Accordion title="Which does Google support?" icon="google">
    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.
  </Accordion>
</AccordionGroup>

<Note>
  Duet projects are scaffolded by [Modaal](https://modaal.dev) — [starting a Duet project](/articles/duet#starting-a-duet-project) walks through the two template cards.
</Note>

## Sources and further reading

* [Compose Multiplatform and Jetpack Compose](https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-multiplatform-and-jetpack-compose.html) — what the framework is relative to androidx Compose
* [Compose Multiplatform 1.8](https://blog.jetbrains.com/kotlin/2025/05/compose-multiplatform-1-8-0-released-compose-multiplatform-for-ios-is-stable-and-production-ready/), [1.10](https://blog.jetbrains.com/kotlin/2026/01/compose-multiplatform-1-10-0/) and [1.12](https://blog.jetbrains.com/kotlin/2026/08/compose-multiplatform-1-12-0/) release posts — iOS stable, Hot Reload, the MCP server
* [iOS accessibility in Compose Multiplatform](https://kotlinlang.org/docs/compose-ios-accessibility.html) — the semantics-to-native mapping and its documented gaps
* [Kotlin Multiplatform on developer.android.com](https://developer.android.com/kotlin/multiplatform) — Google's support statement and KMP Jetpack artifacts
* [Kotlin releases](https://kotlinlang.org/docs/releases.html) — current stable and the Swift Export status
* [Integration with the UIKit framework](https://kotlinlang.org/docs/multiplatform/compose-uikit-integration.html), [Integration with the SwiftUI framework](https://kotlinlang.org/docs/multiplatform/compose-swiftui-integration.html) and [Using Views in Compose](https://developer.android.com/develop/ui/compose/migrate/interoperability-apis/views-in-compose) — the escape-to-native routes for a heavy screen on each platform
* [Interoperability with Swift/Objective-C](https://kotlinlang.org/docs/native-objc-interop.html) — what Kotlin can call on iOS without Swift, and the Swift-only limit
* [The Duet framework on GitHub](https://github.com/modaal-agent/duet) — both flavors, the recording toolchain and the versioned contracts

## Read next

<CardGroup cols={2}>
  <Card title="The best cross-platform frameworks in 2026" icon="map" href="/articles/best-cross-platform-framework">
    The full survey — where the share-the-UI and share-the-logic cells sit on the quadrant.
  </Card>

  <Card title="Handling platform-specific UI" icon="layer-group" href="/articles/cross-platform-ui-parity">
    How Duet keeps two native interfaces honest: the presentation contract and recorded divergence.
  </Card>

  <Card title="Should you share code between iOS and Android?" icon="share-nodes" href="/articles/share-code-ios-android">
    The decision one level up: write twice, share everything, or share the logic.
  </Card>

  <Card title="How to build testable Android apps" icon="vial-circle-check" href="/articles/testable-android-apps">
    The recorded-scenario discipline that gives Duet its verification story, shown in code.
  </Card>
</CardGroup>
