CO

A guide for building shared Compose Multiplatform UI components, managing state, and optimizing performance across Android and Desktop platforms.

Install

mkdir -p .claude/skills/compose-expert && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/1754" && unzip -o skill.zip -d .claude/skills/compose-expert && rm skill.zip

Installs to .claude/skills/compose-expert

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).
498 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Advanced

Key capabilities

  • Organizes shared UI code in commonMain
  • Optimizes recomposition via stable/immutable annotations
  • Implements cross-platform Material3 theming
  • Generates ImageVector icons for shared use
  • Structures state management for multiplatform apps

How it works

Applies a set of architectural rules for grouping UI logic and state handling into shared library modules.

Inputs & outputs

You give it
Compose UI component or state requirement
You get back
Shared UI code block or architectural pattern

When to use compose-expert

  • Refactoring shared UI components into commonMain
  • Optimizing recomposition with @Stable and @Immutable
  • Building custom ImageVector icons
  • Implementing Material3 theming for cross-platform apps

About this skill

Compose Multiplatform Expert

Visual UI patterns for sharing composables across Android and Desktop.

When to Use This Skill

  • Creating or refactoring shared UI components
  • Deciding whether to share UI in commonMain or keep platform-specific
  • Building custom ImageVector icons (robohash pattern)
  • State management: remember, derivedStateOf, produceState
  • Recomposition optimization: visual usage of @Stable/@Immutable
  • Material3 theming and styling
  • Performance: lazy lists, image loading

Delegate to other skills:

  • Navigation structure → android-expert, desktop-expert
  • Kotlin state patterns (StateFlow, sealed classes) → kotlin-expert
  • Build configuration → gradle-expert

Philosophy: Share by Default

Default to commons/commonMain unless platform experts indicate otherwise.

Always Share

  • UI components: Buttons, cards, lists, dialogs, inputs
  • State visualization: Loading, empty, error states
  • Custom icons: ImageVector assets (robohash, custom paths)
  • Theme utilities: Color calculations, style helpers
  • Material3 components: Any UI using Material primitives

Keep Platform-Specific

  • Navigation structure: Bottom nav (Android) vs Sidebar (Desktop)
  • Screen layouts: Platform-specific scaffolding
  • System integrations: File pickers, notifications, share sheets
  • Platform UX: Gestures, keyboard shortcuts, window management

Decision Framework

  1. Uses only Material3 primitives? → Share in commonMain
  2. Requires platform system APIs? → Platform-specific
  3. Pure visual component without navigation? → Share in commonMain
  4. Needs platform UX patterns? → Ask android-expert or desktop-expert

If uncertain, default to sharing - easier to split later than merge.

Shared Composable Anatomy

Structure

@Composable
fun SharedComponent(
    // State parameters (read-only)
    data: DataClass,
    isLoading: Boolean,
    // Event parameters (write-only)
    onAction: () -> Unit,
    // Visual parameters
    modifier: Modifier = Modifier,
    // Optional customization
    colors: ComponentColors = ComponentDefaults.colors()
) {
    // Implementation
}

Pattern: State down, events up

  • Parameters above modifier = required state/events
  • modifier parameter = layout control
  • Parameters below modifier = optional customization

Example: AddButton

@Composable
fun AddButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    text: String = "Add",
    enabled: Boolean = true
) {
    OutlinedButton(
        modifier = modifier,
        enabled = enabled,
        onClick = onClick,
        shape = ActionButtonShape,
        contentPadding = ActionButtonPadding
    ) {
        Text(text = text, textAlign = TextAlign.Center)
    }
}

// Shared constants for consistency
val ActionButtonShape = RoundedCornerShape(20.dp)
val ActionButtonPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp)

Why this works on all platforms:

  • Material3 primitives (OutlinedButton, Text)
  • No platform APIs
  • Configurable through parameters
  • Consistent styling via shared constants

State Management Patterns

remember - Cache Across Recompositions

@Composable
fun ExpandableCard() {
    var isExpanded by remember { mutableStateOf(false) }

    Column {
        IconButton(onClick = { isExpanded = !isExpanded }) {
            Icon(
                if (isExpanded) Icons.Default.ExpandLess else Icons.Default.ExpandMore,
                contentDescription = if (isExpanded) "Collapse" else "Expand"
            )
        }

        if (isExpanded) {
            Text("Expanded content...")
        }
    }
}

Visual pattern: Toggle button → state changes → UI expands/collapses Use for: Simple UI state (toggles, counters, text input)

derivedStateOf - Optimize Frequent Changes

@Composable
fun ScrollToTopButton(listState: LazyListState) {
    // Only recomposes when showButton changes, not every scroll pixel
    val showButton by remember {
        derivedStateOf {
            listState.firstVisibleItemIndex > 0
        }
    }

    if (showButton) {
        FloatingActionButton(onClick = { /* scroll to top */ }) {
            Icon(Icons.Default.ArrowUpward, null)
        }
    }
}

Visual pattern: Scroll position (0, 1, 2...) → boolean (show/hide) → Button visibility Use for: Input changes frequently, derived result changes rarely Performance: Prevents recomposition on every scroll event

produceState - Async to Compose State

@Composable
fun LoadUserProfile(userId: String): State<User?> {
    return produceState<User?>(initialValue = null, userId) {
        value = repository.fetchUser(userId)
    }
}

@Composable
fun ProfileScreen(userId: String) {
    val user by LoadUserProfile(userId)

    when (user) {
        null -> LoadingState("Loading profile...")
        else -> ProfileCard(user!!)
    }
}

Visual pattern: Async operation → state updates → UI reflects changes Use for: Convert Flow, LiveData, callbacks into Compose state Lifecycle: Coroutine cancelled when composable leaves composition

For Kotlin-specific state patterns (StateFlow, sealed classes), see kotlin-expert.

State Hoisting

Move state up to make composables reusable:

// ❌ Stateful - hard to test, can't control externally
@Composable
fun BadSearchBar() {
    var query by remember { mutableStateOf("") }
    TextField(value = query, onValueChange = { query = it })
}

// ✅ Stateless - reusable, testable
@Composable
fun GoodSearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    TextField(
        value = query,
        onValueChange = onQueryChange,
        modifier = modifier
    )
}

@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }

    Column {
        GoodSearchBar(query = query, onQueryChange = { query = it })
        SearchResults(query = query)
    }
}

Principle: State up, events down

  • State: query: String (read-only parameter)
  • Events: onQueryChange: (String) -> Unit (callback parameter)

Recomposition Optimization

Visual Usage of @Immutable

Use @Immutable on data classes passed to composables:

@Immutable
data class UserProfile(val name: String, val avatar: String)

@Composable
fun ProfileCard(profile: UserProfile) {
    // Only recomposes when profile instance changes
    Row {
        RobohashImage(robot = profile.avatar)
        Text(profile.name, style = MaterialTheme.typography.titleMedium)
    }
}

Visual effect: Prevents recomposition when parent recomposes with same data Pattern: Mark parameter data classes as @Immutable Note: For Kotlin language details on @Immutable, see kotlin-expert

Stable Parameters

// ✅ Stable - won't trigger recomposition unless colors instance changes
@Composable
fun ThemedCard(
    content: String,
    colors: CardColors = CardDefaults.colors(),
    modifier: Modifier = Modifier
) {
    Card(colors = colors, modifier = modifier) {
        Text(content)
    }
}

For @Stable annotation details, see kotlin-expert.

Material3 Theming

All shared composables use Material3 for consistency:

@Composable
fun ThemedComponent() {
    val bg = MaterialTheme.colorScheme.background
    val fg = MaterialTheme.colorScheme.onBackground
    val primary = MaterialTheme.colorScheme.primary

    Column(
        modifier = Modifier.background(bg)
    ) {
        Text(
            "Title",
            style = MaterialTheme.typography.headlineMedium,
            color = fg
        )
        Button(
            onClick = { /* ... */ },
            colors = ButtonDefaults.buttonColors(containerColor = primary)
        ) {
            Text("Action")
        }
    }
}

Principles:

  • Colors: MaterialTheme.colorScheme.*
  • Typography: MaterialTheme.typography.*
  • Shapes: MaterialTheme.shapes.*

Theme Detection

@Composable
private fun isLightTheme(): Boolean {
    val background = MaterialTheme.colorScheme.background
    return (background.red + background.green + background.blue) / 3 > 0.5f
}

@Composable
fun ThemedIcon() {
    val isDark = !isLightTheme()
    val tint = if (isDark) Color.White else Color.Black
    Icon(Icons.Default.Face, null, tint = tint)
}

Custom Icons: ImageVector Pattern

Amethyst uses ImageVector for multiplatform icons.

roboBuilder DSL

fun roboBuilder(block: Builder.() -> Unit): ImageVector {
    return ImageVector.Builder(
        name = "Robohash",
        defaultWidth = 300.dp,
        defaultHeight = 300.dp,
        viewportWidth = 300f,
        viewportHeight = 300f
    ).apply(block).build()
}

Building Icons

fun customIcon(fgColor: SolidColor, builder: Builder) {
    builder.addPath(pathData1, fill = fgColor, stroke = Black, strokeLineWidth = 1.5f)
    builder.addPath(pathData2, fill = Black, fillAlpha = 0.4f)
    builder.addPath(pathData3, fill = Black, fillAlpha = 0.2f)
}

private val pathData1 = PathData {
    moveTo(144.5f, 87.5f)
    reflectiveCurveToRelative(-51.0f, 3.0f, -53.0f, 55.0f)
    lineToRelative(16.0f, 16.0f)
    close()
}

@Composable
fun CustomIcon() {
    Image(
        painter = rememberVectorPainter(
            roboBuilder {
                customIcon(SolidColor(Color.Blue), this)
            }
        ),
        contentDescription = "Custom icon"
    )
}

Why ImageVector?

  • Pure Kotlin, no XML
  • Works on Android, Desktop, iOS
  • GPU-accelerated
  • Type-safe

Caching Pattern

object CustomIcons {
    private val cache = mutableMapOf<String, ImageVector>()

    fun get(key: String): ImageVector {
        return cache.getOrPut(key) {
            buildIcon(key)
        }
    }
}

@Composable
fun CachedIcon(key: String) {
    Image(imageVector = CustomIcons.get(key), contentDescription = null)
}
`

---

*Content truncated.*

When not to use it

  • Applications targeting a single platform exclusively
  • UI logic tied heavily to platform-specific system APIs

Prerequisites

Kotlin Multiplatform SDKCompose Multiplatform dependency

Limitations

  • Requires familiarity with KMP architectural constraints
  • Material3 primitives must be strictly followed for sharing
  • Debugging cross-platform state is complex

How it compares

It provides a clear framework for deciding when to share code, avoiding the common 'share-everything' approach that causes platform crashes.

Compared to similar skills

compose-expert side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
compose-expert (this skill)63moReviewAdvanced
android-kotlin74moNo flagsIntermediate
kotlin-expert52moNo flagsAdvanced
android-jetpack-compose-expert44moNo flagsAdvanced

Try saying

Example prompts that trigger this skill in your AI assistant.

More by vitorpamplona

View all by vitorpamplona

kotlin-multiplatform

vitorpamplona

Platform abstraction decision-making for Amethyst KMP project. Guides when to abstract vs keep platform-specific, source set placement (commonMain, jvmAndroid, platform-specific), expect/actual patterns. Covers primary targets (Android, JVM/Desktop, iOS) with web/wasm future considerations. Integrates with gradle-expert for dependency issues. Triggers on: abstraction decisions ("should I share this?"), source set placement questions, expect/actual creation, build.gradle.kts work, incorrect placement detection, KMP dependency suggestions.

32156

kotlin-expert

vitorpamplona

Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.

526

kotlin-coroutines

vitorpamplona

Advanced Kotlin coroutines patterns for AmethystMultiplatform. Use when working with: (1) Structured concurrency (supervisorScope, coroutineScope), (2) Advanced Flow operators (flatMapLatest, combine, merge, shareIn, stateIn), (3) Channels and callbackFlow, (4) Dispatcher management and context switching, (5) Exception handling (CoroutineExceptionHandler, SupervisorJob), (6) Testing async code (runTest, Turbine), (7) Nostr relay connection pools and subscriptions, (8) Backpressure handling in event streams. Delegates to kotlin-expert for basic StateFlow/SharedFlow patterns. Complements nostr-expert for relay communication.

311

nostr-expert

vitorpamplona

Nostr protocol implementation patterns in Quartz (AmethystMultiplatform's KMP Nostr library). Use when working with: (1) Nostr events (creating, parsing, signing), (2) Event kinds and tags, (3) NIP implementations (57 NIPs in quartz/), (4) Event builders and TagArrayBuilder DSL, (5) Nostr cryptography (secp256k1, NIP-44 encryption), (6) Relay communication patterns, (7) Bech32 encoding (npub, nsec, note, nevent). Complements nostr-protocol agent (NIP specs) - this skill provides Quartz codebase patterns and implementation details.

323

gradle-expert

vitorpamplona

Build optimization, dependency resolution, and multi-module KMP troubleshooting for AmethystMultiplatform. Use when working with: (1) Gradle build files (build.gradle.kts, settings.gradle), (2) Version catalog (libs.versions.toml), (3) Build errors and dependency conflicts, (4) Module dependencies and source sets, (5) Desktop packaging (DMG/MSI/DEB), (6) Build performance optimization, (7) Proguard/R8 configuration, (8) Common KMP + Android Gradle issues (Compose conflicts, secp256k1 JNI variants, source set problems).

19

You might also like

android-kotlin

alinaqi

Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing

729

kotlin-expert

vitorpamplona

Advanced Kotlin patterns for AmethystMultiplatform. Flow state management (StateFlow/SharedFlow), sealed hierarchies (classes vs interfaces), immutability (@Immutable, data classes), DSL builders (type-safe fluent APIs), inline functions (reified generics, performance). Use when working with: (1) State management patterns (StateFlow/SharedFlow/MutableStateFlow), (2) Sealed classes or sealed interfaces, (3) @Immutable annotations for Compose, (4) DSL builders with lambda receivers, (5) inline/reified functions, (6) Kotlin performance optimization. Complements kotlin-coroutines agent (async patterns) - this skill focuses on Amethyst-specific Kotlin idioms.

526

android-jetpack-compose-expert

sickn33

Expert guidance for building modern Android UIs with Jetpack Compose, covering state management, navigation, performance, and Material Design 3.

413

reviewing-changes

bitwarden

Guides Android code reviews with type-specific checklists and MVVM/Compose pattern validation. Use when reviewing Android PRs, pull requests, diffs, or local changes involving Kotlin, ViewModel, Composable, Repository, or Gradle files. Triggered by "review PR", "review changes", "check this code", "Android review", or code review requests mentioning bitwarden/android. Loads specialized checklists for feature additions, bug fixes, UI refinements, refactoring, dependency updates, and infrastructure changes.

12

kmp-cmp-app-builder

firdaus1453

>

00

Android Navigation

li-lance

Standards for Jetpack Navigation Compose (Type-safe)

00

Search skills

Search the agent skills registry