Material You: Dynamic Theming and Color in Modern Android Apps

Material You fundamentally changed how Android apps look and feel. Instead of enforcing a fixed brand palette, apps can now adapt their UI colors dynamically based on the user’s wallpaper, system theme, and preferences — while still preserving brand identity and accessibility.

This article explains how Material You (Material 3) works today, how dynamic color actually behaves under the hood, and how to implement it correctly with Jetpack Compose — without relying on deprecated APIs or legacy patterns.

What Is Material You (Material 3)?

Material You, introduced with Android 12, is a user-centric design system where colors, surfaces, and motion adapt to the user’s environment.

At its core is dynamic color:

  • The system extracts colors from the user’s wallpaper
  • Generates multiple tonal palettes
  • Maps those tones to semantic color roles
  • Applies them consistently across system UI and apps

Material 3 builds on this with:

  • Tonal color roles instead of fixed colors
  • Improved accessibility contrast handling
  • Predictable light and dark behavior
  • Surface-based UI design
  • Adaptive components that scale across form factors

How Dynamic Color Works (Conceptually)

When dynamic color is enabled:

  1. The system derives one or more source colors from the wallpaper
  2. These are expanded into tonal palettes
    (primary, secondary, tertiary, neutral, neutralVariant)
  3. Material color roles (such as primaryContainer, surfaceVariant) are mapped from those palettes
  4. Your app consumes roles, not raw colors

This means:

  • You don’t choose exact hex values
  • You design using semantic color roles
  • The system ensures visual harmony and contrast
  • Your UI automatically adapts to light, dark, and contrast changes

When Is Dynamic Color Available?

  • Android 12+ (API 31+) → Full dynamic color support
  • Android 11 and below → Static Material 3 color schemes

Modern Material 3 libraries handle this automatically when configured correctly.

Setting Up Material 3 in Jetpack Compose

Ensure you are using Material 3, not Material 2.

Recommended Dependency Setup

implementation(platform("androidx.compose:compose-bom:2025.01.00"))
implementation("androidx.compose.material3:material3")

Using the Compose BOM is the current best practice and avoids version mismatches.

Enabling Dynamic Color in Compose

Step 1: Create a Theme Wrapper

@Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
useDynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
useDynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (useDarkTheme) {
dynamicDarkColorScheme(LocalContext.current)
} else {
dynamicLightColorScheme(LocalContext.current)
}
}
useDarkTheme -> DarkColorScheme
else -> LightColorScheme
}

MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
shapes = AppShapes,
content = content
)
}

Why this is correct today

  • dynamicLightColorScheme() and dynamicDarkColorScheme() are current, supported APIs
  • No deprecated methods are used
  • Fallback behavior is explicit and predictable
  • Works across all supported Android versions

Defining Fallback Color Schemes

Dynamic color should never be your only strategy.

Your fallback schemes should reflect Material 3’s full color role model, even if minimal.

Light Theme

val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color.White,
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
surface = Color(0xFFFFFBFE),
onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFE7E0EC),
onSurfaceVariant = Color(0xFF49454F)
)

Dark Theme

val DarkColorScheme = darkColorScheme(
primary = Color(0xFFD0BCFF),
onPrimary = Color(0xFF381E72),
primaryContainer = Color(0xFF4F378B),
onPrimaryContainer = Color(0xFFEADDFF),
surface = Color(0xFF1C1B1F),
onSurface = Color(0xFFE6E1E5),
surfaceVariant = Color(0xFF49454F),
onSurfaceVariant = Color(0xFFCAC4D0)
)

These are used when:

  • The device does not support dynamic color
  • The user disables dynamic theming
  • Your app explicitly opts out

Using Color Roles Correctly in Compose

Avoid hardcoded colors inside composables.

❌ Not Recommended

Text(
text = "Pay Now",
color = Color.Blue
)

✅ Recommended

Text(
text = "Pay Now",
color = MaterialTheme.colorScheme.primary
)

Semantic color roles automatically adapt to:

  • Dynamic color
  • Dark mode
  • System contrast settings
  • Future Material updates

Surface-Driven Design (Critical in Material 3)

Material 3 emphasizes surface layering rather than colored backgrounds.

Surface(
color = MaterialTheme.colorScheme.surface,
tonalElevation = 2.dp
) {
Text(
text = "Transaction Successful",
color = MaterialTheme.colorScheme.onSurface
)
}

Why surfaces matter

  • Automatic tonal adjustments
  • Better accessibility contrast
  • Consistent depth and elevation
  • Predictable light and dark behavior

Brand Color + Material You: Best Practice

Material You does not mean losing brand identity.

Recommended approach:

  • Use dynamic color for surfaces and backgrounds
  • Use brand-aligned colors for key actions and emphasis
Button(
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
onClick = { }
) {
Text("Continue")
}

If your brand color is critical:

  • Override only primary
  • Let the system handle secondary, surface, and container roles

Supporting User Preferences

Some users prefer consistency over personalization.

Best practice:

  • Provide a toggle for dynamic color
  • Persist the choice using DataStore or Preferences
  • Rebuild the theme based on the user’s selection

This aligns your app with modern Android UX expectations.

Accessibility Considerations

Material 3 dynamic color automatically:

  • Meets contrast requirements
  • Adjusts tones for dark mode
  • Respects system contrast settings

Still, you should:

  • Avoid low-contrast custom overlays
  • Test text on surfaceVariant
  • Avoid placing text directly on primaryContainer without verifying contrast

Common Mistakes to Avoid

  • Hardcoding colors inside composables
  • Mixing Material 2 components with Material 3 themes
  • Overriding too many color roles
  • Ignoring fallback color schemes
  • Treating dynamic color as an “Android 12-only feature”

Final Thoughts

Material You is not just a visual upgrade — it’s a system-level contract between the OS, the user, and your app.

When implemented correctly:

  • Your app feels native
  • Adapts naturally to user preferences
  • Remains visually consistent across devices
  • Reduces long-term design and maintenance cost

Jetpack Compose and Material 3 make this easier than ever — provided you embrace semantic colors, surface-based design, and dynamic theming as first-class concepts.


Material You: Dynamic Theming and Color in Modern Android Apps was originally published in ProAndroidDev on Medium, where people are continuing the conversation by highlighting and responding to this story.