compose-expert
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.zipInstalls 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).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
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
commonMainor 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
- Uses only Material3 primitives? → Share in
commonMain - Requires platform system APIs? → Platform-specific
- Pure visual component without navigation? → Share in
commonMain - Needs platform UX patterns? → Ask
android-expertordesktop-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
modifierparameter = 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
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.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| compose-expert (this skill) | 6 | 3mo | Review | Advanced |
| android-kotlin | 7 | 4mo | No flags | Intermediate |
| kotlin-expert | 5 | 2mo | No flags | Advanced |
| android-jetpack-compose-expert | 4 | 4mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by vitorpamplona
View all by vitorpamplona →You might also like
android-kotlin
alinaqi
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing
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.
android-jetpack-compose-expert
sickn33
Expert guidance for building modern Android UIs with Jetpack Compose, covering state management, navigation, performance, and Material Design 3.
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.
kmp-cmp-app-builder
firdaus1453
>
Android Navigation
li-lance
Standards for Jetpack Navigation Compose (Type-safe)