Material 3 Theming in Compose: Building Dynamic Color Systems

By Engineering Team • Published: 2025-12-11 • Updated: 2025-12-1114 min read

Material 3ThemingDynamic ColorDesign System

Why Material 3 Changes Everything

Material 3 (also known as Material You) represents a fundamental shift in how Android apps approach theming. Instead of static color palettes, M3 introduces dynamic color extraction from user wallpapers, creating a personalized experience for every user.

For developers migrating from XML, this is a significant upgrade. Traditional theme.xml files with hardcoded colors are replaced by a flexible, programmatic color system that adapts to user preferences automatically.

Setting Up Material 3 in Compose

The foundation of M3 theming is the MaterialTheme composable. Unlike M2, M3 uses a ColorScheme with expanded color roles:

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    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 = Typography,
        content = content
    )
}

Understanding Color Roles

M3 introduces semantic color roles that describe the purpose of colors rather than their appearance:

  • primary/onPrimary: Main brand color and text on top of it
  • secondary/onSecondary: Supporting accent color for less prominent elements
  • tertiary/onTertiary: Contrasting accent for visual interest
  • surface/onSurface: Background colors and text for content areas
  • error/onError: States indicating errors or destructive actions
Note: Always use semantic color roles instead of hardcoded colors. This ensures your UI adapts correctly to light/dark themes and dynamic colors.

Custom Color Schemes

For brand consistency, you can define custom color schemes while still supporting dynamic colors as a fallback:

private val LightColorScheme = lightColorScheme(
    primary = Color(0xFF6750A4),
    onPrimary = Color.White,
    primaryContainer = Color(0xFFEADDFF),
    onPrimaryContainer = Color(0xFF21005D),
    secondary = Color(0xFF625B71),
    // ... other colors
)

private val DarkColorScheme = darkColorScheme(
    primary = Color(0xFFD0BCFF),
    onPrimary = Color(0xFF381E72),
    primaryContainer = Color(0xFF4F378B),
    onPrimaryContainer = Color(0xFFEADDFF),
    // ... other colors
)
← Back to Blog