compose-multiplatform
Compose Multiplatform / KMP patterns - expect/actual composables, platform-specific code, density and font handling cross-target, iOS/Android/Desktop interop.
Install / Use
npx skills add AThevon/genjutsu --skill compose-multiplatformInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Customer SupportSupported Platforms
Our assessment of compose-multiplatform
compose-multiplatform scores 91/100 on our quality scale, 129th of 320 Customer Support skills we index (top 41%).
Its SKILL.md is 16 KB long, well organised into 18 sections with 10 code examples: a thorough specification that gives an agent plenty to work with.
It has 370 GitHub stars, a meaningful sign that others use it.
Maintenance, license and trust
- The repository was last updated 26 days ago, so compose-multiplatform is actively maintained.
- No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
- Its trust signals score 88/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-10-05. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
compose-multiplatform compared with similar skills
All 4 of these similar skills score higher than compose-multiplatform; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| compose-multiplatform (this skill)by AThevon | 91 | 370 | 26d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 90.8k | 19d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 74.4k | today | CLAUDE.md |
| Scraplingby D4Vinci | 100 | 85.7k | today | MCP Server |
| crawl4aiby unclecode | 100 | 84.8k | 9d ago | MCP Server |
Frequently asked questions
- How do I install compose-multiplatform?
- Run
npx skills add AThevon/genjutsu --skill compose-multiplatform. The install tabs above show the steps for each supported agent. - Which AI agents does compose-multiplatform work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is compose-multiplatform safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It declares no license and scores 88/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is compose-multiplatform still maintained?
- The repository was last updated 26 days ago, so compose-multiplatform is actively maintained.
Skill content
View source on GitHubname: compose-multiplatform description: "Compose Multiplatform / KMP patterns - expect/actual composables, platform-specific code, density and font handling cross-target, iOS/Android/Desktop interop." metadata: internal: true
Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in
_jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.
Compose Multiplatform
Compose Multiplatform (CMP) and Kotlin Multiplatform (KMP) patterns for cross-platform UI. Loaded for projects with
org.jetbrains.composeplugin. Foundation:../compose-motion/SKILL.mdfor animation API; this file covers what's specific to writing one Compose codebase for Android + iOS + Desktop + Web.
KMP vs CMP - quick clarification
KMP (Kotlin Multiplatform) is the language and build infrastructure: shared Kotlin code compiled to JVM, Native (iOS, macOS, Linux, Windows), and Wasm. CMP (Compose Multiplatform) is the UI framework on top of KMP, built by JetBrains as a port of Jetpack Compose. You write a single Compose codebase in commonMain that runs on Android, iOS, Desktop (JVM), and Web (Wasm). Platform-specific code lives in androidMain, iosMain, desktopMain, wasmJsMain and is wired in via expect/actual declarations.
Project structure
composeApp/
├── src/
│ ├── commonMain/ ← shared Compose code (most of the app)
│ │ └── kotlin/
│ ├── androidMain/ ← Android-specific (uses Activity, Context)
│ ├── iosMain/ ← iOS-specific (uses UIKit/UIView interop)
│ ├── desktopMain/ ← JVM desktop (uses java.awt/swing if needed)
│ └── wasmJsMain/ ← Wasm web target
├── build.gradle.kts
iosApp/ ← Xcode project consuming the generated framework
androidApp/ ← Android Application module (often merged into composeApp)
The commonMain folder should hold 80-95% of your code in a well-architected CMP project. If iosMain or androidMain start growing past a few hundred lines, you're probably leaking platform concerns into UI logic that could stay shared.
expect/actual pattern
The KMP escape hatch when you genuinely need different implementations per target. Declare the contract once in commonMain, implement it once per target.
// commonMain
expect fun openShareSheet(text: String)
// androidMain
actual fun openShareSheet(text: String) {
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, text)
}
context.startActivity(Intent.createChooser(intent, null))
}
// iosMain
actual fun openShareSheet(text: String) {
val activityVC = UIActivityViewController(
activityItems = listOf(text),
applicationActivities = null
)
UIApplication.sharedApplication.keyWindow
?.rootViewController
?.presentViewController(activityVC, true, null)
}
expect/actual works for top-level functions, classes, type aliases, and properties. The signature in actual must match exactly, including modifiers and default values.
expect/actual for composables
Composables follow the same rules. Useful when a feature needs a platform-specific Compose API (Android RuntimeShader, iOS UIKitView, Desktop SwingPanel).
// commonMain
@Composable
expect fun PlatformBlur(modifier: Modifier = Modifier, content: @Composable () -> Unit)
// androidMain (uses RuntimeShader on Android 13+)
@Composable
actual fun PlatformBlur(modifier: Modifier, content: @Composable () -> Unit) {
Box(modifier.graphicsLayer { renderEffect = blurEffect }) { content() }
}
// iosMain (uses UIVisualEffectView via UIKitView)
@Composable
actual fun PlatformBlur(modifier: Modifier, content: @Composable () -> Unit) {
Box(modifier) {
UIKitView(
factory = {
UIVisualEffectView(
effect = UIBlurEffect.effectWithStyle(
UIBlurEffectStyle.UIBlurEffectStyleSystemMaterial
)
)
},
modifier = Modifier.matchParentSize()
)
content()
}
}
Rule: expect composables should be the exception, not the rule. Most "platform feel" differences can be tuned via tokens (colors, corner radii, spring stiffness) in commonMain, not via separate code paths.
LocalDensity cross-platform
On Android, LocalDensity.current.density reflects the device DPI bucket (1.0, 1.5, 2.0, 3.0...). On iOS, density is computed from UIScreen.scale (typically 2.0 or 3.0 on Retina). On Desktop, density depends on the screen scaling factor (1.0 by default; 2.0 on Retina-class displays; user-configurable on Windows). On Wasm, density follows window.devicePixelRatio.
Don't hardcode Dp to pixel ratios; trust Dp and LocalDensity to handle conversion. If you need an exact pixel value (e.g., for a Canvas draw operation), do the conversion explicitly:
val density = LocalDensity.current
val pxValue = with(density) { 16.dp.toPx() }
Avoid reading density inside hot loops; cache the conversion.
LocalConfiguration and platform-aware UI
LocalConfiguration.current is Android-only and lives in androidMain. For CMP, prefer the cross-platform alternatives:
LocalWindowInfo.current.containerSize- the window/screen size asIntSize, available incommonMain.LocalDensity.current- density, available incommonMain.LocalLayoutDirection.current- LTR / RTL.BoxWithConstraints { ... }- readmaxWidth/maxHeightdirectly inside layout.
If you need real device characteristics (orientation, idiom, model), wrap the access in expect/actual and pass a typed object like PlatformInfo to the common layer.
Fonts cross-platform via Compose Resources
org.jetbrains.compose.resources is the shared resources plugin. Drop fonts in commonMain/composeResources/font/, and the Gradle plugin generates a typed Res accessor.
composeApp/src/commonMain/composeResources/
├── font/
│ ├── Inter-Regular.ttf
│ └── Inter-Bold.ttf
├── drawable/
│ └── logo.xml ← Android XML vector. SVG works on every target EXCEPT Android
└── values/
├── strings.xml ← default locale
└── values-fr/
└── strings.xml ← French overrides (qualifier goes on the DIRECTORY)
Usage in commonMain:
import myproject.composeapp.generated.resources.Inter_Regular
import myproject.composeapp.generated.resources.Inter_Bold
import myproject.composeapp.generated.resources.Res
val InterFamily = FontFamily(
Font(Res.font.Inter_Regular, FontWeight.Normal),
Font(Res.font.Inter_Bold, FontWeight.Bold),
)
Text("Hello", fontFamily = InterFamily)
Same pattern for Res.drawable.logo (image) and Res.string.app_name (localized string via stringResource(...)). Raw files are the exception: there is no generated Res.file.* accessor - you pass a path string to the suspend function Res.readBytes(path), e.g. val bytes = Res.readBytes("files/config.json"), for anything under composeResources/files/.
iOS interop with SwiftUI
CMP produces a UIViewController you can drop into a SwiftUI app. KMP generates a top-level Kotlin function (commonly named MainViewController() or ComposeUIViewController { ... }) that returns a UIViewController. Wrap it with UIViewControllerRepresentable.
// iosMain/kotlin/main.ios.kt
fun MainViewController(): UIViewController = ComposeUIViewController {
AppContent() // commonMain composable
}
// iOS app target
import SwiftUI
import ComposeApp // KMP-generated framework
struct ComposeContent: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
Main_iosKt.MainViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}
struct ContentView: View {
var body: some View { ComposeContent().ignoresSafeArea() }
}
The Kotlin function name gets mangled to Main_iosKt.MainViewController() because the file is main.ios.kt. Check the generated framework headers if the symbol name surprises you.
Android entry point
No interop ceremony on Android. The Activity hosts the common composable directly via setContent { ... }.
// androidApp/src/main/kotlin/MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppContent() // commonMain composable
}
}
}
If you need to pass Context or Activity into commonMain, expose it via a DI graph or an expect class PlatformContext in commonMain with actual class PlatformContext(val context: Context) in androidMain.
Embedding SwiftUI/UIKit inside a Compose iOS view (the reverse direction)
Use UIKitView for a UIView factory or UIKitViewController for a UIViewController factory.
// iosMain
UIKitView(
factory = {
UISwitch().apply {
addTarget(target, action = NSSelectorFromString("onToggle:"), forControlEvents = UIControlEventValueChanged)
}
},
modifier = Modifier.size(48.dp, 32.dp)
)
For SwiftUI views: wrap them in a UIHostingController exposed via a Swift @objc bridge function, then call from Kotlin via the generated headers (cinterop). See references/cmp-interop.md for the full pattern.
Animation cross-platform
All animation APIs (animate*AsState, AnimatedVisibility, updateTransition, SharedTransitionLayout) work identically across targets in CMP 1.7+. Spring tuning written in commonMain produces the same physics on Android and iOS. Gestures (Modifier.draggable, Modifier.pointerInput) work cross-platform with the same API surface.
The animation primer lives in ../compose-motion/SKILL.md. Cross-platform deltas to keep in mind:
- iOS first-frame is slower (Skia bootstrap); a 200ms enter animation feels tighter on Android, slightly delayed on iOS cold start.
- Wasm motion can stutter on first frame (JIT warmup); pre-warm critical paths or hide motion until interactive.
What does NOT work (gotchas)
- Drawer state on iOS: native
ModalNavigationDrawerswipe-to-open from the leading edge conflicts with iOS's back-swipe gesture. Use a button trigger or move the swipe area inward 30dp+. LayoutDirection.Rtlquirks: Android handles RTL natively, iOS Compose had bugs in 1.6 (text alignment, padding inversions). Improved in 1.7+ but verify with real Arabic/Hebrew strings.- Soft keyboard handling:
imePadding()/WindowInsets.imework on Android out of the box and are implemented on iOS too. There is noIOSKeyboardEventListenertype in CMP - the real knob isComposeUIViewControllerConfiguration.onFocusBehavior, which defaults toOnFocusBehavior.FocusableAboveKeyboard(CMP pans the whole Compose view up so the focused field clears the keyboard). If you want to drive the layout yourself withimePadding()/WindowInsets.ime, turn the automatic pan off:
Leaving the default on and applying// iosMain - imports: androidx.compose.ui.window.ComposeUIViewController, // androidx.compose.ui.uikit.OnFocusBehavior fun MainViewController(): UIViewController = ComposeUIViewController( configure = { onFocusBehavior = OnFocusBehavior.DoNothing } ) { AppContent() }imePadding()is the classic double-adjustment bug; same if the SwiftUI parent hosting the controller also adjusts for the keyboard. Color.parseHex(...)does not exist in Compose. UseColor(0xFFRRGGBB)or write a tiny extension.- System fonts on iOS via Compose: do not fal
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
90.8kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
74.4kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
Scrapling
85.7k🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
crawl4ai
84.8kOpen-source web crawler and scraper for LLMs and AI agents: any website into clean, LLM-ready Markdown. Run it yourself, or use Crawl4AI Cloud with one key.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
