This episode explores beyond Android: Compose Multiplatform basics, desktop and web targets, sharing UI components between platforms, and its limitations and practical use cases.

The concepts you've learned — composables, state, recomposition, modifiers — aren't limited to Android. Compose Multiplatform brings the same paradigm to desktop (JVM), web (Wasm and JS), and iOS, sharing UI code between platforms.
This is not a WebView and not a self-rendered framework like Flutter: Compose Multiplatform uses a shared canvas alongside the native UI toolkit on each target. One composable can be used on Android and desktop without changes.
Episode 17 covers Compose Multiplatform basics, desktop and web targets, sharing components, and its limitations and practical use cases.
A Compose Multiplatform project uses Gradle Kotlin Multiplatform. Shared code lives in commonMain, and platform-specific code in each source set:
kotlin {
androidTarget()
jvm("desktop")
wasmJs()
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
}
val desktopMain by getting {
dependencies {
implementation(compose.desktop.currentOs)
}
}
}
}commonMain holds the shared Compose dependencies, and desktopMain adds compose.desktop for the JVM target. Android, desktop, and web use the same UI from commonMain.
Platform-dependent code is declared with expect and actual:
expect fun getPlatformName(): String
// di desktopMain
actual fun getPlatformName(): String = "Desktop"
// di androidMain
actual fun getPlatformName(): String = "Android"expect declares a shared API, and actual provides the implementation per platform. This pattern is used for things like clipboard, storage, and notifications.
The desktop target runs as a standalone JVM application:
./gradlew :composeApp:run./gradlew :composeApp:run launches the desktop application in development mode. For distribution, packageDistributionForCurrentOS generates an installer per OS.
For web, Compose compiles to WebAssembly:
./gradlew :composeApp:wasmJsBrowserRun./gradlew :composeApp:wasmJsBrowserRun runs the application in the browser. Its output is static, so it can be deployed to any hosting — a pattern that suits dashboards and internal tools.
UI foundations that aren't platform-specific can be shared directly:
@Composable
fun KartuPesan(pesan: String) {
Card(Modifier.fillMaxWidth().padding(8.dp)) {
Text(pesan, Modifier.padding(12.dp))
}
}KartuPesan only uses general Compose APIs, so it compiles for Android, desktop, and web at once. Your design system from episode 6 can be used across targets with almost no changes.
Parts that touch platform APIs — cameras, sensors, the Android view framework — still require expect/actual. For UI, Compose Multiplatform provides cross-platform versions of many components, but some Android-specific features still need that boundary.
Compose Multiplatform is best suited for: applications with large shared business logic across targets, internal tools like dashboards and admin panels, and teams that already master Kotlin and Compose. Shared UI code meaningfully cuts maintenance costs.
For iOS with a consumer target that wants a full native experience, the iOS platform is still maturing. For web that heavily depends on SEO and full accessibility, traditional web frameworks remain a better fit. Evaluate these boundaries before committing to a target.
Episode 17 explored beyond Android: the Compose Multiplatform project structure with commonMain and expect-actual, running applications on desktop with run and on web with wasmJsBrowserRun, sharing UI components across targets, and the limitations and practical use cases where Compose Multiplatform benefits most.
Key takeaways:
./gradlew :composeApp:run runs desktop, wasmJsBrowserRun for web.In episode 18 we will discuss custom layouts and modifiers — creating custom layout composables, advanced modifiers and pointer input, reusable UI primitives, and building complex components from scratch.