Theming & Dark Mode
How theming works in Compose
MaterialTheme provides three things to everything inside it: a color scheme, typography, and shapes. Components read from these automatically, so changing the theme restyles your whole app consistently.
Defining colors
private val LightColors = lightColorScheme(
primary = Color(0xFF7C3AED),
secondary = Color(0xFFA855F7),
background = Color(0xFFFAF8FF)
)
private val DarkColors = darkColorScheme(
primary = Color(0xFFC4B5FD),
background = Color(0xFF12101A)
)
A theme wrapper with dark mode
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(), // follows the device
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = Typography(),
content = content
)
}
isSystemInDarkTheme() automatically returns true when the user has dark mode on, so your app switches with the system.
Using theme values (never hard-code)
Text(
"Title",
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.titleLarge
)
Surface(color = MaterialTheme.colorScheme.surface) { /* ... */ }
Because you reference colorScheme rather than fixed colors, the same code looks correct in both light and dark mode.
Typography and shapes
val typography = Typography(
titleLarge = TextStyle(fontSize = 22.sp, fontWeight = FontWeight.Bold),
bodyMedium = TextStyle(fontSize = 15.sp)
)
Dynamic color (Android 12+)
On Android 12+, you can derive your color scheme from the user’s wallpaper with dynamicLightColorScheme(context) for a personalised look.
Common mistakes
- Hard-coding
Color.White/Color.Black— broken in dark mode. Use theme colors. - Forgetting to wrap the app in your
AppTheme. - Mixing raw hex colors throughout the UI instead of centralising them.
Summary: Wrap your app in aMaterialThemewith light/dark color schemes, read colors and fonts fromMaterialTheme, and useisSystemInDarkTheme()so dark mode works automatically.