Skip to main content
skill Claude Code CX Codex Cursor GitHub Copilot

compose-multiplatform-patterns — Claude Code skill

Patterns for Compose Multiplatform and Jetpack Compose in KMP projects, covering state, navigation, theming, performance, and platform-specific UI.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add affaan-m/ECC --skill compose-multiplatform-patterns -a claude-code
Open on GitHub

What compose-multiplatform-patterns does

This skill guides shared UI work for Android, iOS, Desktop, and Web with Compose Multiplatform and Jetpack Compose. It covers ViewModel with a single StateFlow state object, collecting state in Compose, event sinks, type-safe navigation, dialogs, and bottom sheets.

The material also shows slot-based composables, modifier ordering, expect/actual platform UI, stable types, LazyColumn keys, derivedStateOf, and ways to reduce unnecessary recomposition. Material 3 and dynamic theming are included. Use it when an agent is building or refactoring Compose UI in a KMP or Android project. Skip it for projects that do not use Compose.

What it helps with

  • model screen state with ViewModel and StateFlow
  • add type-safe navigation to a KMP project
  • separate platform UI with expect/actual
  • reduce unnecessary recomposition in LazyColumn
  • build reusable composables with slot APIs

Example prompts for the agent

  • Add an item list screen with ViewModel, StateFlow, and search.
  • Set up type-safe navigation for HomeRoute and DetailRoute.
  • Review this LazyColumn and reduce unnecessary recomposition.

How to install compose-multiplatform-patterns

1Skills CLI (Vercel)

npx skills add affaan-m/ECC --skill compose-multiplatform-patterns -a claude-code

2Manually: put the files in place

~/.claude/skills/compose-multiplatform-patterns/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/compose-multiplatform-patterns/

From the same set146

The whole set →
video-editing affaan-m AI-assisted workflow for editing, trimming, and enriching real footage. Connects raw recordings with FFmpeg cuts, Remotion overlays, and ElevenLabs voiceovers. Claude Code CX Codex Cursor +1 6,500installs docker-patterns affaan-m A skill with Dockerfile and Docker Compose patterns: local dev stacks, networking, volumes, container hardening. Claude Code CX Codex Cursor +1 6,300installs frontend-patterns affaan-m A skill for React and Next.js work: component patterns, state, data fetching, and render performance. Use it when building or reviewing UI. Claude Code CX Codex Cursor +1 5,900installs backend-patterns affaan-m A skill with backend patterns for Node.js, Express and Next.js API routes: layering, caching, transactions, error handling. Claude Code CX Codex Cursor +1 5,900installs coding-standards affaan-m Baseline cross-project coding conventions for naming, immutability, and clean code principles. Useful for code reviews and refactoring. Claude Code CX Codex Cursor +1 5,600installs api-design affaan-m Provides REST API design standards covering resource naming, status codes, pagination, and error payloads. Use it when creating or auditing API contracts. Claude Code CX Codex Cursor +1 5,500installs rust-testing affaan-m A skill for writing Rust tests: unit, integration, async, property-based, mocks and coverage. Useful when the project runs on TDD. Claude Code CX Codex Cursor +1 5,500installs laravel-security affaan-m This skill reviews Laravel authentication, Eloquent safety, CSRF, XSS, APIs, and production settings. Use it for security reviews or deployment checks. Claude Code CX Codex Cursor +1 5,400installs

Similar

better-colors jakubkrehel Audits color systems, generates ramps, and maps semantic tokens to roles. Use it when hardcoded hex values pile up or text contrast needs verifiable measurement. Claude Code CX Codex Cursor +1 32,200installs verify react Validates code changes against React repository requirements before committing. Runs Prettier, linter, Flow type checks, and tests. Claude Code CX Codex Cursor +1 1,200installs web-design-reviewer official github This skill inspects a running website, spots layout, responsive, and accessibility issues, and can fix them in the source code. Use it when a UI review should lead to verified code changes. GitHub Copilot 13,957installs better-layout jakubkrehel A skill that sets grouping, alignment, reading order and responsive structure, then stress-tests the result by resizing, translating and mirroring it for RTL. Claude Code CX Codex Cursor +1 30,400installs frontend-design official anthropics This skill guides intentional visual design for new interfaces and redesigns. Use it when a generic generated UI is not enough. Claude Code Claude.ai 1,025,790installs design-taste-frontend Leonxlnx This skill directs the design of public pages and work screens, then checks the result in a browser through screenshots. Use it when an interface feels generic or unfinished. Claude Code CX Codex Cursor +1 673,100installs prototype Matt Pocock Creates throwaway prototypes for checking state logic or exploring UI before committing the decision to the main codebase. Claude Code 986,201installs hyperframes heygen-com Instructs coding agents on creating, validating, and rendering HTML-based videos using the HyperFrames framework and CLI. Claude Code CX Codex Cursor +1 1,200,000installs

Questions about compose-multiplatform-patterns

How do I install compose-multiplatform-patterns in Claude Code?

Run npx skills add affaan-m/ECC --skill compose-multiplatform-patterns -a claude-code. The skill file will be placed at ~/.claude/skills/compose-multiplatform-patterns/SKILL.md.

What does compose-multiplatform-patterns do?

It provides patterns for Compose UI state, navigation, theming, performance, and platform-specific code in KMP projects, with examples for Android, iOS, Desktop, and Web.

What projects is this skill for?

It is for Compose Multiplatform or Jetpack Compose projects that use ViewModel, StateFlow, type-safe navigation, reusable composables, or recomposition tuning.

Source text

SKILL.md · MIT
---
name: compose-multiplatform-patterns
description: Compose Multiplatform and Jetpack Compose patterns for KMP projects — state management, navigation, theming, performance, and platform-specific UI. Use when building Compose or Jetpack Compose UI, state, navigation, or theming in a KMP project.
metadata:
  origin: ECC
---

# Compose Multiplatform Patterns

Patterns for building shared UI across Android, iOS, Desktop, and Web using Compose Multiplatform and Jetpack Compose. Covers state management, navigation, theming, and performance.

## When to Activate

- Building Compose UI (Jetpack Compose or Compose Multiplatform)
- Managing UI state with ViewModels and Compose state
- Implementing navigation in KMP or Android projects
- Designing reusable composables and design systems
- Optimizing recomposition and rendering performance

## State Management

### ViewModel + Single State Object

Use a single data class for screen state. Expose it as `StateFlow` and collect in Compose:

```kotlin
data class ItemListState(
    val items: List<Item> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null,
    val searchQuery: String = ""
)

class ItemListViewModel(
    private val getItems: GetItemsUseCase
) : ViewModel() {
    private val _state = MutableStateFlow(ItemListState())
    val state: StateFlow<ItemListState> = _state.asStateFlow()

    fun onSearch(query: String) {
        _state.update { it.copy(searchQuery = query) }
        loadItems(query)
    }

    private fun loadItems(query: String) {
        viewModelScope.launch {
            _state.update { it.copy(isLoading = true) }
            getItems(query).fold(
                onSuccess = { items -> _state.update { it.copy(items = items, isLoading = false) } },
                onFailure = { e -> _state.update { it.copy(error = e.message, isLoading = false) } }
            )
        }
    }
}
```

### Collecting State in Compose

```kotlin
@Composable
fun ItemListScreen(viewModel: ItemListViewModel = koinViewModel()) {
    val state by viewModel.state.collectAsStateWithLifecycle()

    ItemListContent(
        state = state,
        onSearch = viewModel::onSearch
    )
}

@Composable
private fun ItemListContent(
    state: ItemListState,
    onSearch: (String) -> Unit
) {
    // Stateless composable — easy to preview and test
}
```

### Event Sink Pattern

For complex screens, use a sealed interface for events instead of multiple callback lambdas:

```kotlin
sealed interface ItemListEvent {
    data class Search(val query: String) : ItemListEvent
    data class Delete(val itemId: String) : ItemListEvent
    data object Refresh : ItemListEvent
}

// In ViewModel
fun onEvent(event: ItemListEvent) {
    when (event) {
        is ItemListEvent.Search -> onSearch(event.query)
        is ItemListEvent.Delete -> deleteItem(event.itemId)
        is ItemListEvent.Refresh -> loadItems(_state.value.searchQuery)
    }
}

// In Composable — single lambda instead of many
ItemListContent(
    state = state,
    onEvent = viewModel::onEvent
)
```

## Navigation

### Type-Safe Navigation (Compose Navigation 2.8+)

Define routes as `@Serializable` objects:

```kotlin
@Serializable data object HomeRoute
@Serializable data class DetailRoute(val id: String)
@Serializable data object SettingsRoute

@Composable
fun AppNavHost(navController: NavHostController = rememberNavController()) {
    NavHost(navController, startDestination = HomeRoute) {
        composable<HomeRoute> {
            HomeScreen(onNavigateToDetail = { id -> navController.navigate(DetailRoute(id)) })
        }
        composable<DetailRoute> { backStackEntry ->
            val route = backStackEntry.toRoute<DetailRoute>()
            DetailScreen(id = route.id)
        }
        composable<SettingsRoute> { SettingsScreen() }
    }
}
```

### Dialog and Bottom Sheet Navigation

Use `dialog()` and overlay patterns instead of imperative show/hide:

```kotlin
NavHost(navController, startDestination = HomeRoute) {
    composable<HomeRoute> { /* ... */ }
    dialog<ConfirmDeleteRoute> { backStackEntry ->
        val route = backStackEntry.toRoute<ConfirmDeleteRoute>()
        ConfirmDeleteDialog(
            itemId = route.itemId,
            onConfirm = { navController.popBackStack() },
            onDismiss = { navController.popBackStack() }
        )
    }
}
```

## Composable Design

### Slot-Based APIs

Design composables with slot parameters for flexibility:

```kotlin
@Composable
fun AppCard(
    modifier: Modifier = Modifier,
    header: @Composable () -> Unit = {},
    content: @Composable ColumnScope.() -> Unit,
    actions: @Composable RowScope.() -> Unit = {}
) {
    Card(modifier = modifier) {
        Column {
            header()
            Column(content = content)
            Row(horizontalArrangement = Arrangement.End, content = actions)
        }
    }
}
```

### Modifier Ordering

Modifier order matters — apply in this sequence:

```kotlin
Text(
    text = "Hello",
    modifier = Modifier
        .padding(16.dp)          // 1. Layout (padding, size)
        .clip(RoundedCornerShape(8.dp))  // 2. Shape
        .background(Color.White) // 3. Drawing (background, border)
        .clickable { }           // 4. Interaction
)
```

## KMP Platform-Specific UI

### expect/actual for Platform Composables

```kotlin
// commonMain
@Composable
expect fun PlatformStatusBar(darkIcons: Boolean)

// androidMain
@Composable
actual fun PlatformStatusBar(darkIcons: Boolean) {
    val systemUiController = rememberSystemUiController()
    SideEffect { systemUiController.setStatusBarColor(Color.Transparent, darkIcons) }
}

// iosMain
@Composable
actual fun PlatformStatusBar(darkIcons: Boolean) {
    // iOS handles this via UIKit interop or Info.plist
}
```

## Performance

### Stable Types for Skippable Recomposition

Mark classes as `@Stable` or `@Immutable` when all properties are stable:

```kotlin
@Immutable
data class ItemUiModel(
    val id: String,
    val title: String,
    val description: String,
    val progress: Float
)
```

### Use `key()` and Lazy Lists Correctly

```kotlin
LazyColumn {
    items(
        items = items,
        key = { it.id }  // Stable keys enable item reuse and animations
    ) { item ->
        ItemRow(item = item)
    }
}
```

### Defer Reads with `derivedStateOf`

```kotlin
val listState = rememberLazyListState()
val showScrollToTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 5 }
}
```

### Avoid Allocations in Recomposition

```kotlin
// BAD — new lambda and list every recomposition
items.filter { it.isActive }.forEach { ActiveItem(it, onClick = { handle(it) }) }

// GOOD — key each item so callbacks stay attached to the right row
val activeItems = remember(items) { items.filter { it.isActive } }
activeItems.forEach { item ->
    key(item.id) {
        ActiveItem(item, onClick = { handle(item) })
    }
}
```

## Theming

### Material 3 Dynamic Theming

```kotlin
@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            if (darkTheme) dynamicDarkColorScheme(LocalContext.current)
            else dynamicLightColorScheme(LocalContext.current)
        }
        darkTheme -> darkColorScheme()
        else -> lightColorScheme()
    }

    MaterialTheme(colorScheme = colorScheme, content = content)
}
```

## Anti-Patterns to Avoid

- Using `mutableStateOf` in ViewModels when `MutableStateFlow` with `collectAsStateWithLifecycle` is safer for lifecycle
- Passing `NavController` deep into composables — pass lambda callbacks instead
- Heavy computation inside `@Composable` functions — move to ViewModel or `remember {}`
- Using `LaunchedEffect(Unit)` as a substitute for ViewModel init — it re-runs on configuration change in some setups
- Creating new object instances in composable parameters — causes unnecessary recomposition

## References

See skill: `android-clean-architecture` for module structure and layering.
See skill: `kotlin-coroutines-flows` for coroutine and Flow patterns.

© affaan-m · MIT · affaan-m/ECC

See also

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor