material3-design-system — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited material3-design-system (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill guides implementation of Google's Material Design 3 (MD3 / Material You) using Jetpack Compose and Compose Multiplatform for KMP projects (Android + Desktop).
Attribution: This skill is adapted from and gives credit to [hamen/material-3-skill](https://github.com/hamen/material-3-skill) by Hamen. The original skill covers web and Flutter targets. This KMP edition strips all web/CSS/Flutter patterns and replaces them with pure Compose Multiplatform APIs, adds the audit report system, and extends the reference set for adaptive layout, navigation, and versioning.
Scope: Compose-only. All examples useandroidx.compose.material3APIs — the same API surface is available in KMPcommonMainviaorg.jetbrains.compose.material3:material3(declared explicitly inlibs.versions.toml). No web CSS, no@material/webelements, no Flutter.
| Principle | What it means in Compose |
|---|---|
| Personal | Dynamic color from user wallpaper (Android 12+). Static fallback for Desktop. |
| Adaptive | WindowSizeClass drives layout changes across compact → expanded screens. |
| Expressive | Spring-based motion, shape morphing, emphasized typography. |
androidx.compose.material3.Material3Adaptive scaffold APIs.Dp literals.All MD3 values come through MaterialTheme. Never hardcode raw values inline.
| Token category | Access in Compose |
|---|---|
| Color | MaterialTheme.colorScheme.* |
| Typography | MaterialTheme.typography.* |
| Shape | MaterialTheme.shapes.* |
| Spacing | Define a Dimens object (no built-in API) |
What are you building?
Full app scaffold → AppTheme setup + references/theming-and-dynamic-color.md
Single component → references/component-catalog.md
Custom color theme → references/color-system.md
Typography / fonts → references/typography-and-shape.md
Navigation structure → references/navigation-patterns.md
Adaptive layout → references/layout-and-responsive.mdFull details in references/color-system.md.
| Role | Token | Primary Usage |
|---|---|---|
| Primary | colorScheme.primary | FAB, key buttons, active states |
| On Primary | colorScheme.onPrimary | Text/icons on primary |
| Primary Container | colorScheme.primaryContainer | Tonal buttons, selected chips |
| On Primary Container | colorScheme.onPrimaryContainer | Text on primary container |
| Secondary | colorScheme.secondary | Less prominent accents, filters |
| Secondary Container | colorScheme.secondaryContainer | Recessive fills |
| Tertiary | colorScheme.tertiary | Contrasting accent sections |
| Surface | colorScheme.surface | Cards, sheets, menus |
| Surface Container | colorScheme.surfaceContainer | Navigation areas |
| On Surface | colorScheme.onSurface | Body text, icons |
| On Surface Variant | colorScheme.onSurfaceVariant | Placeholder, helper text |
| Outline | colorScheme.outline | Input borders, dividers |
| Error | colorScheme.error | Error states |
Full details in references/typography-and-shape.md.
| Category | Styles | Usage |
|---|---|---|
| Display | L / M / S | Hero text, large numbers |
| Headline | L / M / S | Screen/section headers |
| Title | L / M / S | Toolbar titles, card headers |
| Body | L / M / S | Paragraph text, descriptions |
| Label | L / M / S | Buttons, chips, captions |
// ✅ Always via MaterialTheme
Text("Title", style = MaterialTheme.typography.titleLarge)
Text("Body", style = MaterialTheme.typography.bodyMedium)
// ❌ Never inline
Text("Title", fontSize = 22.sp, fontWeight = FontWeight.Normal)| Token | Corner Radius | Typical Components |
|---|---|---|
shapes.extraSmall | 4dp | Chips, snackbars |
shapes.small | 8dp | Text fields, menus |
shapes.medium | 12dp | Cards |
shapes.large | 16dp | FABs, nav drawer |
shapes.extraLarge | 28dp | Dialogs, bottom sheets |
MD3 communicates depth through tonal surface color, not drop shadows.
| Level | Compose API | Tonal Offset | Use |
|---|---|---|---|
| 0 | Elevation.Level0 / 0.dp | None | Flat surfaces at rest |
| 1 | Elevation.Level1 / 1.dp | +5% primary | Elevated cards |
| 2 | Elevation.Level2 / 3.dp | +8% primary | Menus, nav bar |
| 3 | Elevation.Level3 / 6.dp | +11% primary | FAB, dialogs |
// Cards respect tonal elevation automatically via surfaceTonalElevation
ElevatedCard(elevation = CardDefaults.elevatedCardElevation(defaultElevation = 6.dp)) { }Full details in references/typography-and-shape.md §Motion.
| Easing | Compose | Usage |
|---|---|---|
| Emphasized | CubicBezierEasing(0.2f, 0f, 0f, 1f) | Elements staying on screen |
| Emphasized Decelerate | CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f) | Entering screen |
| Emphasized Accelerate | CubicBezierEasing(0.3f, 0f, 0.8f, 0.15f) | Leaving screen |
| Standard | FastOutSlowInEasing | Utility animations |
| Token | Duration | Usage |
|---|---|---|
| Short | 100–200ms | Icon/color state changes |
| Medium | 300–400ms | Component expand/collapse |
| Long | 400–500ms | Screen-level transitions |
| Component | Compose API | Category |
|---|---|---|
| Button (Filled) | Button {} | Actions |
| Button (Tonal) | FilledTonalButton {} | Actions |
| Button (Outlined) | OutlinedButton {} | Actions |
| Button (Text) | TextButton {} | Actions |
| FAB | FloatingActionButton {} | Actions |
| Extended FAB | ExtendedFloatingActionButton {} | Actions |
| Icon Button | IconButton {}, FilledIconButton {} | Actions |
| Segmented Button | SegmentedButton {} | Actions |
| Card | Card {}, ElevatedCard {}, OutlinedCard {} | Containment |
| Dialog | AlertDialog {}, Dialog {} | Containment |
| Bottom Sheet | ModalBottomSheet {} | Sheets |
| Snackbar | SnackbarHost {} | Communication |
| Progress | CircularProgressIndicator(), LinearProgressIndicator() | Communication |
| Badge | BadgedBox {} | Communication |
| Checkbox | Checkbox() | Input |
| RadioButton | RadioButton() | Input |
| Switch | Switch() | Input |
| Slider | Slider(), RangeSlider() | Input |
| TextField | TextField(), OutlinedTextField() | Input |
| Chips | FilterChip, AssistChip, InputChip, SuggestionChip | Input |
| TopAppBar | TopAppBar, CenterAlignedTopAppBar, LargeTopAppBar | Navigation |
| Navigation Bar | NavigationBar {} | Navigation |
| Navigation Rail | NavigationRail {} | Navigation |
| Navigation Drawer | ModalNavigationDrawer {} | Navigation |
| Tabs | TabRow {}, ScrollableTabRow {} | Navigation |
Full Compose API + examples: references/component-catalog.md
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is Android 12+ only — always falls back to static on Desktop
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
shapes = AppShapes,
content = content
)
}For Compose Multiplatform Desktop: RemoveBuild.VERSION_SDK_INTcheck and always useLightColorScheme/DarkColorScheme. Dynamic color has no JVM equivalent.
Full theming guide: references/theming-and-dynamic-color.md
Color(0xFF...) in composables — always MaterialTheme.colorScheme.*fontSize, fontFamily, fontWeight — always MaterialTheme.typography.*RoundedCornerShape(12.dp) — always MaterialTheme.shapes.*AppTheme at the root — never in individual screensisSystemInDarkTheme()Scaffold — it handles topBar, bottomBar, FAB, snackbarHost, paddingDimens objectWhen the user asks for an audit, a compliance check, or passes code / a screen name with the audit argument, run a full MD3 compliance report using the template below.
audit [screen name or paste code here]
audit HomeScreen
audit <paste composable code>
check this screen against material 3
run md3 auditOutput the report in this exact structure:
╔══════════════════════════════════════════════════════════╗
║ MD3 COMPLIANCE AUDIT — [Screen/File Name] ║
║ KMP / Compose Multiplatform Edition ║
╚══════════════════════════════════════════════════════════╝
Score: [X / 10] Grade: [A / B / C / D / F]
┌─────────────────────────────────────────────────────────┐
│ CATEGORY RESULTS │
└─────────────────────────────────────────────────────────┘
[✅ / ⚠️ / ❌] COLOR SYSTEM [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] TYPOGRAPHY [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] SHAPE [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] SPACING [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] ELEVATION [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] COMPONENTS [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] LAYOUT & ADAPTIVE [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] NAVIGATION [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] MOTION & ANIMATION [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] DARK MODE [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] ACCESSIBILITY [PASS / WARN / FAIL]
[✅ / ⚠️ / ❌] THEMING SETUP [PASS / WARN / FAIL]
┌─────────────────────────────────────────────────────────┐
│ FINDINGS │
└─────────────────────────────────────────────────────────┘
[findings listed per category — see check rules below]
┌─────────────────────────────────────────────────────────┐
│ FIXES │
└─────────────────────────────────────────────────────────┘
[corrected code snippets for every ❌ and ⚠️]#### 1. COLOR SYSTEM
| Check | Pass condition | Fail condition |
|---|---|---|
| No hardcoded colors in composables | MaterialTheme.colorScheme.* used | Color(0xFF…) literal in UI code |
| No swapped semantic roles | error used for errors only | error used for success/warning |
| Both schemes defined | LightColorScheme + DarkColorScheme exist | Only one scheme present |
| Dynamic color has static fallback | if (dynamicColor && SDK >= S) with else branch | Dynamic color used unconditionally |
Extended colors use CompositionLocal | LocalExtendedColors pattern | Raw color passed as parameter |
| Desktop: no dynamic color API | jvmMain uses static scheme | dynamicDarkColorScheme() in jvmMain |
#### 2. TYPOGRAPHY
| Check | Pass condition | Fail condition |
|---|---|---|
| No inline font sizes | MaterialTheme.typography.* used | fontSize = 16.sp inline |
| No inline font weights | MaterialTheme.typography.* used | fontWeight = FontWeight.Bold inline |
Custom font loaded via Res.font.* | Font(Res.font.*) for KMP | Hardcoded path or fontFamily literal |
| All 15 styles defined if custom typography | Typography(displayLarge = …, labelSmall = …) | Missing styles in custom Typography |
Body text uses bodyLarge/bodyMedium | Correct role applied | displayLarge on body copy |
#### 3. SHAPE
| Check | Pass condition | Fail condition |
|---|---|---|
No inline RoundedCornerShape | MaterialTheme.shapes.* used | RoundedCornerShape(12.dp) inline |
| Shape token matches component | Cards use shapes.medium, FABs use shapes.extraLarge | FAB with shapes.small |
Custom shapes defined in AppShapes | val AppShapes = Shapes(…) in theme | Shape overrides scattered in UI |
#### 4. SPACING
| Check | Pass condition | Fail condition |
|---|---|---|
Spacing uses Dimens object | Dimens.md, Dimens.lg, etc. | Scattered 16.dp, 24.dp literals |
| Values are multiples of 4dp | 4, 8, 12, 16, 24, 32, 48dp | 15.dp, 7.dp, 11.dp literals |
| Screen margins consistent | Dimens.screenHorizontal used | Mixed margin values per screen |
#### 5. ELEVATION
| Check | Pass condition | Fail condition |
|---|---|---|
| Tonal elevation used | tonalElevation / CardDefaults.elevatedCardElevation() | Modifier.shadow(8.dp) for depth |
| Shadow used only for busy backgrounds | Rare Modifier.shadow with clear reason | Shadows on all cards for styling |
#### 6. COMPONENTS
| Check | Pass condition | Fail condition |
|---|---|---|
Only one Button (filled) per section | Single primary action | Multiple filled buttons per section |
FAB in Scaffold.floatingActionButton | Scaffold(floatingActionButton = {…}) | FAB positioned manually with Box |
Scaffold used on every screen | Scaffold {} wraps each screen | No Scaffold, manual layout |
AlertDialog for destructive actions | confirmButton + dismissButton both present | No dismiss option on destructive dialog |
Lists use ListItem | ListItem(headlineContent = …) | Custom Row replacing ListItem |
| Buttons use correct emphasis hierarchy | Filled → Tonal → Elevated → Outlined → Text | Multiple filled buttons, no hierarchy |
#### 7. LAYOUT & ADAPTIVE
| Check | Pass condition | Fail condition |
|---|---|---|
WindowSizeClass used | calculateWindowSizeClass() or currentWindowAdaptiveInfo() | Fixed-width if (isTablet) hack |
| No hardcoded breakpoints | WindowWidthSizeClass.* enum | if (width > 600.dp) check |
| Canonical layout pattern used | Feed / List-Detail / Supporting Pane | None of the canonical patterns applied |
| Edge-to-edge enabled | enableEdgeToEdge() in Activity | Status bar not handled |
WindowInsets applied | Modifier.statusBarsPadding() or Scaffold | Content hidden behind system bars |
| Adaptive API used for list-detail | NavigableListDetailPaneScaffold | Manual Row reimplementing list-detail |
#### 8. NAVIGATION
| Check | Pass condition | Fail condition |
|---|---|---|
| Nav component matches window size | NavigationBar on compact, NavigationRail on medium, drawer on expanded | Bottom nav on tablet |
| Bottom nav has 3–5 items | Destination count in range | 2 or 6+ items in NavigationBar |
launchSingleTop = true | Present on all nav clicks | Duplicate back-stack entries possible |
saveState + restoreState | Present on nav clicks | Tab scroll position lost |
| Type-safe routes | @Serializable objects/classes | String literal routes |
NavController not in ViewModel | Navigate via UiEffect | navController injected into ViewModel |
#### 9. MOTION & ANIMATION
| Check | Pass condition | Fail condition |
|---|---|---|
| Easing matches direction | Entering: EmphasizedDecelerate, Leaving: EmphasizedAccelerate | Symmetric easing for enter/exit |
animate*AsState has label = | label = "colorAnimation" present | Missing label parameter |
| Duration ≤ 500ms screen, ≤ 300ms component | Within limits | tween(800ms) on a button |
| Spring for interactions, tween for transitions | spring() on drag/toggle, tween() on nav | tween() on swipe gesture |
#### 10. DARK MODE
| Check | Pass condition | Fail condition |
|---|---|---|
isSystemInDarkTheme() wired to theme | darkTheme = isSystemInDarkTheme() | Hard-coded darkTheme = false |
| Both schemes tested | Code has DarkColorScheme defined | Only LightColorScheme present |
@Preview(uiMode = UI_MODE_NIGHT_YES) on previews | Both light and dark previews | Only light mode previews |
#### 11. ACCESSIBILITY
| Check | Pass condition | Fail condition |
|---|---|---|
| Touch targets ≥ 48×48dp | Icons/buttons have Modifier.size(48.dp) or larger | Modifier.size(24.dp) as only modifier on clickable |
Icon-only buttons have contentDescription | Non-null description | contentDescription = null on icon button |
| Contrast ratio ≥ 4.5:1 (normal text) | M3 baseline palette used | Custom palette not validated |
| No color-only state communication | Icon/text also changes state | Only color changes for selected state |
| Semantic roles applied | Modifier.semantics { role = Role.Button } where needed | Custom clickable without role |
#### 12. THEMING SETUP
| Check | Pass condition | Fail condition |
|---|---|---|
Single MaterialTheme call at root | AppTheme wraps root composable only | MaterialTheme called in individual screens |
AppTheme has darkTheme + dynamicColor params | Both parameters present | Theme has no parameters |
AppTypography, AppShapes defined | Separate files in theme/ | Defaults used (MaterialTheme() with no args) |
Desktop uses expect/actual for theme | rememberColorScheme split across source sets | Android-only dynamic color call in commonMain |
| Score | Grade | Meaning |
|---|---|---|
| 10 / 10 | A | Full MD3 compliance — production ready |
| 8–9 / 10 | B | Minor warnings — good with small fixes |
| 6–7 / 10 | C | Several violations — needs attention |
| 4–5 / 10 | D | Major issues — significant rework needed |
| 0–3 / 10 | F | Critical violations — MD3 not followed |
Each category scores 1 point: ✅ PASS = 1pt, ⚠️ WARN = 0.5pt, ❌ FAIL = 0pt. Round to nearest 0.5.
| File | Contents |
|---|---|
| references/color-system.md | All 29 color roles, light/dark schemes, dynamic color, custom extensions |
| references/theming-and-dynamic-color.md | AppTheme setup, dynamic color, KMP (Android+Desktop) theme split |
| references/typography-and-shape.md | Full 15-style type scale, font setup, shape tokens, elevation, motion |
| references/component-catalog.md | All 30+ components with Compose API + code examples |
| references/layout-and-responsive.md | WindowSizeClass, adaptive scaffolds, canonical layouts, spacing tokens |
| references/navigation-patterns.md | NavBar, Rail, Drawer, Tabs — when to use each, Compose wiring |
The compose.material3, compose.ui, compose.foundation shorthand accessors previously provided by the Compose Multiplatform Gradle plugin are deprecated as of CMP 1.10.0-beta01.
| Old (deprecated) | New (explicit libs entry) |
|---|---|
implementation(compose.material3) | implementation(libs.compose.material3) |
implementation(compose.ui) | implementation(libs.compose.ui) |
implementation(compose.foundation) | implementation(libs.compose.foundation) |
material-icons-core is no longer transitive (since CMP 1.8.2)Starting with CMP 1.8.2, the implicit dependency on material-icons-core was removed. If your project uses Icons.Default.* or any Material icon, add it explicitly.
gradle/libs.versions.toml[versions]
kotlin = "2.1.21"
agp = "8.10.0"
composeMultiplatform = "1.8.2" # org.jetbrains.compose plugin version
coroutines = "1.10.2"
lifecycle = "2.9.0"
[libraries]
# ✅ Correct module for commonMain
# The CMP plugin maps this → androidx.compose.material3 on Android automatically
compose-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "composeMultiplatform" }
compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "composeMultiplatform" }
compose-foundation = { module = "org.jetbrains.compose.foundation:foundation", version.ref = "composeMultiplatform" }
compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "composeMultiplatform" }
# Declare explicitly — no longer a transitive dep since CMP 1.8.2
compose-material-icons-core = { module = "org.jetbrains.compose.material:material-icons-core", version.ref = "composeMultiplatform" }
compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "composeMultiplatform" }
[plugins]
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
androidApplication = { id = "com.android.application", version.ref = "agp" }
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }composeApp/build.gradle.ktskotlin {
sourceSets {
commonMain.dependencies {
// ✅ Use libs.* — NOT the deprecated compose.* plugin accessors
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
implementation(libs.compose.ui)
implementation(libs.compose.material3)
implementation(libs.compose.material.icons.core) // explicit since CMP 1.8.2
}
androidMain.dependencies {
implementation(libs.compose.ui.tooling.preview)
}
}
}| Source set | Module in toml | What Gradle actually resolves |
|---|---|---|
commonMain | org.jetbrains.compose.material3:material3 | JetBrains multiplatform artifact |
androidMain (via CMP plugin metadata) | same toml entry | androidx.compose.material3:material3 |
jvmMain (Desktop) | same toml entry | JetBrains desktop artifact |
You never need to manually switch to androidx.compose.material3 in build files — the CMP Gradle plugin metadata handles the platform mapping transparently.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.