
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:
- The system derives one or more source colors from the wallpaper
- These are expanded into tonal palettes
(primary, secondary, tertiary, neutral, neutralVariant) - Material color roles (such as primaryContainer, surfaceVariant) are mapped from those palettes
- 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.