Distopia
A Kotlin Multiplatform (KMP) design system built on top of
Material Design 3 baseline. Targets Android, iOS, Desktop (JVM), and Web
(JS/WASM) through Compose Multiplatform. All tokens are derived from the
M3 specification and exposed via
WriteopiaTheme.
How to use
Wrap your root composable with WriteopiaTheme. It injects both the M3 MaterialTheme and the custom WriteopiaColors semantic layer.
@Composable fun App() { WriteopiaTheme { YourContent() } } // Access M3 tokens val primary = MaterialTheme.colorScheme.primary // Access Writeopia semantic tokens val colors = WriteopiaTheme.colorScheme val bg = colors.globalBackground
Color
28-token M3 baseline palette. Switch between light and dark using the toggle above — these values mirror Theme.kt exactly.
Semantic tokens
WriteopiaColors is a custom semantic layer on top of M3 accessed via
WriteopiaTheme.colorScheme. Use these tokens to stay consistent across the
app without reaching into raw M3 roles.
| Token | Light | Dark | Usage |
|---|---|---|---|
| globalBackground | #FFFBFE |
#1C1B1F |
App-wide canvas background |
| lightBackground | #E7E0EC |
#49454F |
Sidebar, panel backgrounds |
| textLight | #1C1B1F |
#E6E1E5 |
Primary text color |
| textLighter | #49454F |
#CAC4D0 |
Secondary / subdued text |
| highlight | #E7E0EC |
#49454F |
Hover / highlight surfaces |
| selectedBg | primaryContainer 50% |
primaryContainer 35% |
Selected list item background |
| dividerColor | #CAC4D0 |
#49454F |
Horizontal/vertical dividers |
| linkColor | #6750A4 |
#D0BCFF |
Hyperlinks and inline actions |
| defaultButton | #6750A4 |
#D0BCFF |
Default/fallback button color |
| cardBg | surfaceContainer |
surfaceContainer |
Note card background color |
| searchBackground | #FFFBFE |
#49454F |
Search bar input background |
Typography
Full M3 type scale — 15 styles. Font: Roboto (system default on Android, KMP default elsewhere). Defined in Type.kt and applied via MaterialTheme.typography.
| Token | Sample | Spec |
|---|---|---|
| displayLarge | Aa | 57sp · 64sp · -0.25sp · Regular |
| displayMedium | Aa | 45sp · 52sp · 0sp · Regular |
| displaySmall | Aa | 36sp · 44sp · 0sp · Regular |
| headlineLarge | Aa | 32sp · 40sp · 0sp · Regular |
| headlineMedium | Aa | 28sp · 36sp · 0sp · Regular |
| headlineSmall | Aa | 24sp · 32sp · 0sp · Regular |
| titleLarge | Title Large | 22sp · 28sp · 0sp · Regular |
| titleMedium | Title Medium | 16sp · 24sp · 0.15sp · Medium |
| titleSmall | Title Small | 14sp · 20sp · 0.1sp · Medium |
| bodyLarge | Body Large text sample | 16sp · 24sp · 0.5sp · Regular |
| bodyMedium | Body Medium text sample | 14sp · 20sp · 0.25sp · Regular |
| bodySmall | Body Small text sample | 12sp · 16sp · 0.4sp · Regular |
| labelLarge | Label Large | 14sp · 20sp · 0.1sp · Medium |
| labelMedium | Label Medium | 12sp · 16sp · 0.5sp · Medium |
| labelSmall | Label Small | 11sp · 16sp · 0.5sp · Medium |
Shape
M3 baseline shape scale. Applied via MaterialTheme.shapes. Defined in Shape.kt.
Buttons
WButton wraps all five M3 button roles plus a Destructive variant for
error-state actions. Use WButtonVariant to select the emphasis level.
// WButtonVariant WButton(text = "Save", onClick = { }, variant = WButtonVariant.Filled) WButton(text = "Cancel", onClick = { }, variant = WButtonVariant.Tonal) WButton(text = "Learn more", onClick = { }, variant = WButtonVariant.Outlined) WButton(text = "Skip", onClick = { }, variant = WButtonVariant.Text) WButton(text = "Delete", onClick = { }, variant = WButtonVariant.Destructive)
WButton( text = "New note", onClick = { }, variant = WButtonVariant.Filled, leadingIcon = Icons.Default.Add, leadingIconDescription = "Add", )
FAB
Floating Action Button for primary actions. Use M3 FloatingActionButton or ExtendedFloatingActionButton directly.
FloatingActionButton(onClick = { }) { Icon(Icons.Default.Add, contentDescription = "Add") } ExtendedFloatingActionButton( onClick = { }, icon = { Icon(Icons.Default.Add, contentDescription = null) }, text = { Text("New") } )
Chips
Four chip roles: Assist, Filter, Input, Suggestion. Use M3 AssistChip, FilterChip, InputChip, SuggestionChip.
AssistChip(onClick = { }, label = { Text("Assist") }) FilterChip(selected = true, onClick = { }, label = { Text("Filter") }) InputChip(selected = false, onClick = { }, label = { Text("Input") }) SuggestionChip(onClick = { }, label = { Text("Suggestion") })
Text Fields
Filled and Outlined variants. Use M3 TextField and OutlinedTextField.
var text by remember { mutableStateOf("") } TextField( value = text, onValueChange = { text = it }, label = { Text("Label") } ) OutlinedTextField( value = text, onValueChange = { text = it }, label = { Text("Label") } )
Cards
Three card styles: Elevated (default shadow), Filled (surface variant), Outlined (border only). Use M3 Card, ElevatedCard, OutlinedCard.
ElevatedCard(modifier = Modifier.width(200.dp)) { Text("Elevated", style = MaterialTheme.typography.titleMedium) Text("Content", style = MaterialTheme.typography.bodyMedium) } OutlinedCard(modifier = Modifier.width(200.dp)) { Text("Outlined") }
List Items
Use M3 ListItem. The selected state uses WriteopiaTheme.colorScheme.selectedBg.
ListItem( headlineContent = { Text("Design tokens") }, supportingContent = { Text("Updated 2 min ago") }, leadingContent = { Icon(Icons.Default.Description, contentDescription = null) }, modifier = Modifier.background(WriteopiaTheme.colorScheme.selectedBg) )
Switch
Toggle binary states. Use M3 Switch.
var checked by remember { mutableStateOf(false) } Switch( checked = checked, onCheckedChange = { checked = it } )
Dialog
Confirmation and alert dialogs. Use M3 AlertDialog.
AlertDialog( onDismissRequest = { showDialog = false }, title = { Text("Delete note?") }, text = { Text("This note will be permanently deleted.") }, confirmButton = { WButton(text = "Delete", onClick = { }, variant = WButtonVariant.Destructive) }, dismissButton = { WButton(text = "Cancel", onClick = { }, variant = WButtonVariant.Text) } )
Snackbar
Brief messages with an optional action. Use M3 Snackbar via SnackbarHost.
val snackbarHostState = remember { SnackbarHostState() } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) } ) { // trigger: LaunchedEffect(Unit) { snackbarHostState.showSnackbar( message = "Note moved to Trash", actionLabel = "Undo" ) } }
Progress Indicators
Linear and Circular. Use M3 LinearProgressIndicator and CircularProgressIndicator.
// Determinate LinearProgressIndicator(progress = { 0.6f }) // Indeterminate LinearProgressIndicator() // Circular CircularProgressIndicator()
Badge
Notification indicators — dot or count. Use M3 BadgedBox.
// Dot badge BadgedBox(badge = { Badge() }) { Icon(Icons.Default.Email, contentDescription = "Email") } // Count badge BadgedBox(badge = { Badge { Text("5") } }) { Icon(Icons.Default.Notifications, contentDescription = "Notifications") }