What you'll learn
A theme is what turns a functional app into your app. Because every component reads from MaterialTheme tokens, defining a theme once restyles the entire app — and makes dark mode almost automatic. We'll build a custom theme, add a dark scheme, and switch on Material You's dynamic color.
By the end you'll be able to:
- Define a custom color scheme, typography, and shapes
- Wrap your app in a reusable
AppTheme - Support dark mode that follows the system setting
- Enable dynamic color (Material You) on Android 12+
A custom MaterialTheme
A theme is three things: a color scheme, a typography set, and a shapes set. New projects come with a ui/theme/ folder holding exactly these files. You feed them into MaterialTheme, wrapped in your own composable — by convention AppTheme — so you configure it in one place:
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(), // follow the OS setting
content: @Composable () -> Unit,
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
shapes = AppShapes,
content = content,
)
}Then wrap your whole UI in it, once, from MainActivity:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme { // wrap the whole app once
HomeScreen() // every composable inside reads these tokens
}
}
}
}Key idea
Light & dark schemes
Compose gives you two builders — lightColorScheme() and darkColorScheme() — each taking the full set of roles (primary, onPrimary, background, surface, and so on). You define both; Compose picks the right one at runtime:
private val LightColors = lightColorScheme(
primary = Color(0xFF6366F1),
onPrimary = Color.White,
secondary = Color(0xFF8B5CF6),
background = Color(0xFFFDFDFF),
surface = Color(0xFFF5F5FA),
)
private val DarkColors = darkColorScheme(
primary = Color(0xFFA5B4FC),
onPrimary = Color(0xFF1E1B4B),
secondary = Color(0xFFC4B5FD),
background = Color(0xFF0B0B12),
surface = Color(0xFF14141F),
)Tip
Supporting dark mode
The magic line is isSystemInDarkTheme() — a composable that returns whether the device is in dark mode. Feed it into your theme (as the darkTheme default) and your app follows the system setting with zero extra work. Because your components use token pairs like surface / onSurface, they recolor themselves correctly in both modes:
Watch out
Color.Black text will vanish on a dark background — the classic dark-mode bug. If something looks wrong in dark mode, hunt for a hard-coded color and replace it with the right colorScheme token.Dynamic color (Material You)
On Android 12+, Material You can generate your app's color scheme from the user's wallpaper, so your app harmonizes with their personalized system colors. Use dynamicLightColorScheme() / dynamicDarkColorScheme(), guarded by a version check, and fall back to your brand colors on older devices:
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true, // Material You, Android 12+
content: @Composable () -> Unit,
) {
val context = LocalContext.current
val colors = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
darkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(colorScheme = colors, content = content)
}Dynamic color is a lovely touch, but keep a strong branded fallback — and remember that your carefully chosen palette gets replaced when it's on. Many apps make it a user setting.
Recap & quick check
Key takeaways
- A theme = color scheme + typography + shapes, fed into MaterialTheme via your own AppTheme composable.
- Wrap the whole app in AppTheme once in setContent; every composable then reads the same tokens.
- Define both lightColorScheme() and darkColorScheme(); pick between them with isSystemInDarkTheme().
- Token pairs (surface/onSurface) make dark mode work automatically — never hard-code colors.
- Dynamic color (Material You, Android 12+) derives the scheme from the wallpaper, with a branded fallback.
Quick check
1. What three token sets make up a Compose theme?
2. How does your app follow the system's dark mode setting?
3. Why does dark mode 'just work' when you use theme tokens?
4. Text disappears in dark mode. Likely cause?
5. What is dynamic color (Material You)?
That completes Phase 2 — you can build beautiful, interactive, themed screens! 🎉 In Phase 3 we connect screens together and give the app a real architecture, starting with Navigation in Compose.