Compose Multiplatform for Shared UI
Learn how to share a single Kotlin UI across Android, iOS, desktop, and web with Compose Multiplatform, JetBrains declarative UI framework.
Published on • August 17, 2026
AI Assistant

Maintaining two native UIs — a Kotlin Android app and a SwiftUI iOS app — means every feature ships twice. Compose Multiplatform (CMP) is a declarative UI framework for Kotlin that lets you write that UI once and run it on Android, iOS, desktop (Windows, macOS, Linux), and the web, all from a single codebase. It is built on top of Jetpack Compose, so Android developers feel right at home, while Kotlin Multiplatform gives you access to native APIs when you need them.
In this tutorial, you will learn how to scaffold a Compose Multiplatform project, share UI between Android and iOS, and add desktop and web targets. Key technologies: Kotlin Multiplatform, Jetpack Compose, Gradle, and the composeApp module structure.
Prerequisites
- JDK 17+
- Android Studio (with the Compose Multiplatform and Kotlin Multiplatform plugins)
- Kotlin 2.0+ and Gradle 8+
- Basic familiarity with Kotlin and declarative UI concepts
Core Content
Scaffold the project
Compose Multiplatform projects use a standard Kotlin Multiplatform layout with a shared composeApp module. The easiest way to start is the project wizard at jb.gg/start-cmp, which generates this structure:
composeApp/
src/
commonMain/kotlin/ # Shared UI + logic for all targets
androidMain/kotlin/ # Android-specific code
iosMain/kotlin/ # iOS-specific code
desktopMain/kotlin/ # Desktop entry point
wasmJsMain/kotlin/ # Web (Wasm) entry point
build.gradle.kts
gradle/
libs.versions.toml
The commonMain source set is where the magic happens — every Composable you put there is available on every platform.
Define targets in the build file
The composeApp/build.gradle.kts declares which platforms you want to target. For Android + iOS + Desktop + Web:
kotlin {
androidTarget()
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { it.binaries.framework {
baseName = "ComposeApp"
isStatic = true
} }
jvm("desktop")
wasmJs {
moduleName = "composeApp"
browser()
}
sourceSets {
val desktopMain by getting
androidMain.dependencies {
implementation(compose.preview)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
}
}
}
Each target compiles the shared commonMain code and links it against its native runtime — Android uses Jetpack Compose, iOS uses Skia with a native shell, and desktop targets the JVM.
Write shared UI
Here’s a shared screen that works everywhere. Notice it’s just regular Jetpack Compose — no platform-specific code:
@Composable
fun GreetingScreen(name: String, onIncrement: () -> Unit, count: Int) {
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize().padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(text = "Hello, $name!", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = onIncrement) {
Text("Count is $count")
}
}
}
}
The UI state is driven by callbacks so the same screen is fully testable and reusable across targets.
Platform entry points
Each platform needs a thin entry point that launches the shared UI. On Android it’s an Activity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}
On iOS, a MainViewController wraps the same Composable:
fun MainViewController() = ComposeUIViewController { App() }
And on desktop, a main function with the application DSL:
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "Compose Multiplatform",
state = rememberWindowState(width = 800.dp, height = 600.dp)
) {
App()
}
}
Calling native APIs from shared code
Because Compose Multiplatform is built on Kotlin Multiplatform, you can access platform capabilities through expect/actual. For example, reading a platform identifier:
// commonMain
expect fun platformName(): String
// androidMain
actual fun platformName(): String = "Android: ${Build.VERSION.SDK_INT}"
// iosMain
actual fun platformName(): String = "iOS: ${UIDevice.currentDevice.systemName}"
Putting It All Together
The complete project runs the identical App() Composable on Android, iOS, desktop, and web. Compose Multiplatform shares most of its API with Jetpack Compose, so the Android experience is identical to standard Jetpack Compose development, while iOS, desktop, and Wasm targets get the same declarative code compiled natively. Desktop apps are JVM-based with hardware-accelerated rendering, and web support (in Beta since the 1.9.0 release) targets Kotlin/Wasm for predictable performance in the browser.
Conclusion & Next Steps
You’ve scaffolded a Compose Multiplatform project, written shared UI in commonMain, added per-platform entry points, and learned how to access native APIs with expect/actual.
Next Steps: try sharing an entire app between iOS and Android, embed native views like MKMapView via interop, and explore the official samples for navigation, state management, and material 3 theming across all four targets.
References:
- JetBrains — Compose Multiplatform repository. https://github.com/JetBrains/compose-multiplatform
- Compose Multiplatform — Getting started. https://jb.gg/start-cmp
- JetBrains — Compose Multiplatform 1.9.0: Compose for Web (Beta). https://blog.jetbrains.com/kotlin/2025/09/compose-multiplatform-1-9-0-compose-for-web-beta/