Skip to content
Development
Skill

/compose-expert

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

From plugin
amethyst
1.6k30 skills3 commands
Install
$ npx -y skills add vitorpamplona/amethyst --skill compose-expert --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/compose-expert

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

compose-expert.SKILL.md
name: compose-expert
description: 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).

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
Read more
Ships withamethyst

Nostr client for Android

Get the whole plugin

Other skills on amethyst.