$npx -y skills add ehmo/platform-design-skills --skill androidMaterial Design 3 and Android platform guidelines. Use when building Android apps with Jetpack Compose or XML layouts, implementing Material You, navigation, or accessibility. Triggers on tasks involving Android UI, Compose components, dynamic color, or Material Design compliance
| 1 | # Android Platform Design Guidelines — Material Design 3 |
| 2 | |
| 3 | ## 1. Material You & Theming [CRITICAL] |
| 4 | |
| 5 | ### 1.1 Dynamic Color |
| 6 | |
| 7 | Enable dynamic color derived from the user's wallpaper. Dynamic color is the default on Android 12+ and should be the primary theming strategy. |
| 8 | |
| 9 | ```kotlin |
| 10 | // Compose: Dynamic color theme |
| 11 | @Composable |
| 12 | fun AppTheme( |
| 13 | darkTheme: Boolean = isSystemInDarkTheme(), |
| 14 | dynamicColor: Boolean = true, |
| 15 | content: @Composable () -> Unit |
| 16 | ) { |
| 17 | val colorScheme = when { |
| 18 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { |
| 19 | val context = LocalContext.current |
| 20 | if (darkTheme) dynamicDarkColorScheme(context) |
| 21 | else dynamicLightColorScheme(context) |
| 22 | } |
| 23 | darkTheme -> darkColorScheme() |
| 24 | else -> lightColorScheme() |
| 25 | } |
| 26 | MaterialTheme( |
| 27 | colorScheme = colorScheme, |
| 28 | typography = AppTypography, |
| 29 | content = content |
| 30 | ) |
| 31 | } |
| 32 | ``` |
| 33 | |
| 34 | ```xml |
| 35 | <!-- XML: Dynamic color in themes.xml --> |
| 36 | <style name="Theme.App" parent="Theme.Material3.DayNight.NoActionBar"> |
| 37 | <item name="dynamicColorThemeOverlay">@style/ThemeOverlay.Material3.DynamicColors.DayNight</item> |
| 38 | </style> |
| 39 | ``` |
| 40 | |
| 41 | **Rules:** |
| 42 | - R1.1: Always provide a fallback static color scheme for devices below Android 12. |
| 43 | - R1.2: Never hardcode color hex values in components. Always reference color roles from the theme. |
| 44 | - R1.3: Test with at least 3 different wallpapers to verify dynamic color harmony. |
| 45 | |
| 46 | ### 1.2 Color Roles |
| 47 | |
| 48 | Material 3 defines a structured set of color roles. Use them semantically, not aesthetically. |
| 49 | |
| 50 | | Role | Usage | On-Role | |
| 51 | |------|-------|---------| |
| 52 | | `primary` | Key actions, active states, FAB | `onPrimary` | |
| 53 | | `primaryContainer` | Less prominent primary elements | `onPrimaryContainer` | |
| 54 | | `secondary` | Supporting UI, filter chips | `onSecondary` | |
| 55 | | `secondaryContainer` | Navigation bar active indicator | `onSecondaryContainer` | |
| 56 | | `tertiary` | Accent, contrast, complementary | `onTertiary` | |
| 57 | | `tertiaryContainer` | Input fields, less prominent accents | `onTertiaryContainer` | |
| 58 | | `surface` | Backgrounds, cards, sheets | `onSurface` | |
| 59 | | `surfaceVariant` | Decorative elements, dividers | `onSurfaceVariant` | |
| 60 | | `error` | Error states, destructive actions | `onError` | |
| 61 | | `errorContainer` | Error backgrounds | `onErrorContainer` | |
| 62 | | `outline` | Borders, dividers | — | |
| 63 | | `outlineVariant` | Subtle borders | — | |
| 64 | | `inverseSurface` | Snackbar background | `inverseOnSurface` | |
| 65 | |
| 66 | ```kotlin |
| 67 | // Correct: semantic color roles |
| 68 | Text( |
| 69 | text = "Error message", |
| 70 | color = MaterialTheme.colorScheme.error |
| 71 | ) |
| 72 | Surface(color = MaterialTheme.colorScheme.errorContainer) { |
| 73 | Text(text = "Error detail", color = MaterialTheme.colorScheme.onErrorContainer) |
| 74 | } |
| 75 | |
| 76 | // WRONG: hardcoded colors |
| 77 | Text(text = "Error", color = Color(0xFFB00020)) // Anti-pattern |
| 78 | ``` |
| 79 | |
| 80 | **Rules:** |
| 81 | - R1.4: Every foreground element must use the matching `on` color role for its background (e.g., `onPrimary` text on `primary` background). |
| 82 | - R1.5: Use `surface` and its variants for backgrounds. Never use `primary` or `secondary` as large background areas. |
| 83 | - R1.6: Use `tertiary` sparingly for accent and complementary contrast only. |
| 84 | |
| 85 | ### 1.3 Light and Dark Themes |
| 86 | |
| 87 | Support both light and dark themes. Respect the system setting by default. |
| 88 | |
| 89 | ```kotlin |
| 90 | // Compose: Detect system theme |
| 91 | val darkTheme = isSystemInDarkTheme() |
| 92 | ``` |
| 93 | |
| 94 | **Rules:** |
| 95 | - R1.7: Always support both light and dark themes. Never ship light-only. |
| 96 | - R1.8: Dark theme surfaces use elevation-based tonal mapping, not pure black (#000000). Use `surface` color roles which handle this automatically. |
| 97 | - R1.9: Provide a manual theme override in app settings (System / Light / Dark). |
| 98 | |
| 99 | ### 1.4 Custom Color Seeds |
| 100 | |
| 101 | When branding requires custom colors, provide a seed color and generate tonal palettes using Material Theme Builder. |
| 102 | |
| 103 | ```kotlin |
| 104 | // Custom color scheme with brand seed |
| 105 | private val BrandLightColorScheme = lightColorScheme( |
| 106 | primary = Color(0xFF1B6D2F), |
| 107 | onPrimary = Color(0xFFFFFFFF), |
| 108 | primaryContainer = Color(0xFFA4F6A8), |
| 109 | onPrimaryContainer = Color(0xFF002107), |
| 110 | // ... generate full palette from seed |
| 111 | ) |
| 112 | ``` |
| 113 | |
| 114 | **Rules:** |
| 115 | - R1.10: Generate tonal palettes from seed colors using Material Theme Builder. Never manually pick individual tones. |
| 116 | - R1.11: When using custom colors, still support dynamic color as the default and use custom colors as fallback. |
| 117 | |
| 118 | --- |
| 119 | |
| 120 | ## 2. Navigation [CRITICAL] |
| 121 | |
| 122 | ### 2.1 Navigation Bar (Bottom) |
| 123 | |
| 124 | The primary navigation pattern for phones with 3-5 top-level destinations. |
| 125 | |
| 126 | ```kotlin |
| 127 | // Compose: Navigation Bar |
| 128 | NavigationBar { |
| 129 | items.forEachI |