How to architect, build, and ship a design system library for Compose Multiplatform from scratch. Use this skill whenever creating a component library, designing a theme system, building reusable UI components, setting up a design system module structure, creating a catalog/showcase app, or publishing a Compose library. Also triggers on: 'compose design system', 'component library', 'theme architecture', 'design tokens', 'color scheme from seed', 'catalog app', 'compose library publishing', 'dark mode strategy', 'semantic colors', 'design system module structure'.
Architect, build, and ship a complete design system library for Compose Multiplatform. No Material dependency. Your tokens, your components, your rules.
A design system is more than components — it's a systematic approach to UI consistency. In Compose, it's built on Foundation primitives with a theme layer providing design tokens.
Design System
├── Theme (tokens): Colors, Typography, Spacing, Borders, Corners, Shadows, Animation
├── Foundation (primitives): Surface, Divider, Indication, WindowSize
├── Components: Actions, Inputs, Display, Navigation, Feedback, Data, Layout, Content
└── Catalog App (documentation + showcase)
Global tokens → Semantic tokens → Component tokens
#FF6B6B → primary → buttonBackground
16.sp → bodyMedium → textFieldFont
8.dp → sm → chipPadding
data class MyColorScheme(
val primary: Color, val onPrimary: Color,
val secondary: Color, val tertiary: Color,
val surface: Color, val onSurface: Color,
val background: Color, val border: Color,
val error: Color, val warning: Color, val success: Color,
) {
companion object {
fun fromSeed(seed: Color, isDark: Boolean = false): MyColorScheme {
// HSL hue rotation: secondary = +120°, tertiary = +240°, accent = +180°
}
}
}
// Don't just invert. Design each mode:
fun light() = MyColorScheme(
surface = Color.White, onSurface = Color.Black,
background = Color(0xFFF5F0E8), // warm paper
// shadows = black offset
)
fun dark() = MyColorScheme(
surface = Color(0xFF1A1A1A), onSurface = Color(0xFFE0E0E0),
background = Color(0xFF121212),
// shadows = white glow (black shadows are invisible on dark!)
)
my-design-system/
├── src/
│ ├── commonMain/ # ALL components here
│ │ └── kotlin/
│ │ ├── components/
│ │ ├── foundation/
│ │ └── theme/
│ ├── androidMain/ # Haptics, insets
│ ├── desktopMain/ # Cursor icons
│ └── wasmJsMain/ # Web-specific
├── catalog/ # Showcase app (separate module)
└── build.gradle.kts
kotlin {
androidTarget(); jvm("desktop"); wasmJs { browser() }; iosArm64(); iosSimulatorArm64()
sourceSets {
commonMain.dependencies {
implementation(compose.foundation)
implementation(compose.ui)
implementation(compose.runtime)
// NO compose.material3!
}
}
}
// ✅ Colors, borders, corners come from theme
@Composable
fun Button(onClick: () -> Unit, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Box(modifier = modifier.background(MyTheme.colors.primary).border(MyTheme.borders.default, MyTheme.colors.border)) { content() }
}
// ❌ Never pass style as parameters
@Composable
fun Button(backgroundColor: Color = ..., borderWidth: Dp = ...) // NO
// ✅ Flexible
@Composable fun Card(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit)
// ❌ Rigid
@Composable fun Card(title: String, subtitle: String, body: String) // NO
// ✅ Caller owns state
@Composable fun Toggle(checked: Boolean, onCheckedChange: (Boolean) -> Unit)
// ❌ Internal state
@Composable fun Toggle(initialChecked: Boolean = false) // NO
enum class Severity(val label: String, val icon: ImageVector) {
Info("Note", Lucide.Info), Success("Tip", Lucide.Lightbulb),
Warning("Warning", Lucide.TriangleAlert), Danger("Danger", Lucide.OctagonAlert),
}
// Reuse across Callout, Banner, Notification, Toast
@Composable
fun ComponentShowcase(name: String, description: String, code: String, content: @Composable () -> Unit) {
Card {
BasicText(name, style = typography.headline)
BasicText(description, style = typography.body)
Box(background = colors.background) { content() } // Live demo
var showCode by remember { mutableStateOf(false) }
if (showCode) Code(code = code, language = "kotlin")
}
}
| ❌ Don't | ✅ Do Instead |
|---|---|
| Import Material 3 in your design system | Use Foundation only |
| Pass style parameters to components | Read from theme |
| Hold state inside components | State hoisting — caller owns state |
| Use string params for content | Composable slots |
| Invert colors for dark mode | Design each mode intentionally |
| Skip the catalog app | Build it — it IS your documentation |
Use implementation() for transitive API deps (icons) | Use api() |
@Composable functions with Modifier parameterMyTheme.colors, .typography, .spacing)api() not implementation()// Screenshot test
@Test fun buttonVariants() {
composeTestRule.setContent { MyTheme { Column { /* variants */ } } }
composeTestRule.onRoot().captureToImage()
}
// Interaction test
@Test fun toggleChangesState() {
var checked = false
composeTestRule.setContent { Toggle(checked = checked, onCheckedChange = { checked = it }) }
composeTestRule.onNodeWithTag("toggle").performClick()
assertTrue(checked)
}