📰 Introduction
Dark Mode has become a standard feature in modern Android apps. It enhances visual comfort, saves battery on AMOLED screens, and aligns with the user’s system preferences. With Jetpack Compose, implementing dark and light themes becomes clean, modular, and developer-friendly.
In this tutorial, you’ll learn:
- 🔧 How to create light and dark themes in Jetpack Compose
- 🔁 How to toggle between themes using a switch
- 💾 How to save theme preference using Jetpack DataStore
- 🎨 Best practices for Dark Mode design
- ✅ Kotlin code examples for real implementation

🎨 Create Light and Dark Themes
Define color schemes in your Theme.kt
file:
kotlinCopyEditprivate val LightColors = lightColorScheme(
primary = Blue500,
onPrimary = White,
background = White,
onBackground = Black,
surface = White,
onSurface = Black
)
private val DarkColors = darkColorScheme(
primary = Blue200,
onPrimary = Black,
background = DarkGray,
onBackground = White,
surface = DarkGray,
onSurface = White
)
⚙️ Apply Theme Using MaterialTheme
Use Compose’s MaterialTheme
to wrap your UI:
kotlinCopyEdit@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
content = content
)
}
💡
isSystemInDarkTheme()
respects the user’s device settings automatically.
🔁 Toggle Dark Mode with a Switch
kotlinCopyEdit@Composable
fun DarkModeToggle(isDark: Boolean, onToggle: () -> Unit) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "Dark Mode")
Switch(checked = isDark, onCheckedChange = { onToggle() })
}
}
💾 Persist User Preference with DataStore
Use Jetpack DataStore to save user settings.
1. Setup DataStore
kotlinCopyEditval Context.dataStore by preferencesDataStore("user_prefs")
val DARK_MODE_KEY = booleanPreferencesKey("dark_mode_enabled")
2. Save & Retrieve
kotlinCopyEditsuspend fun saveThemePreference(context: Context, isDark: Boolean) {
context.dataStore.edit { settings ->
settings[DARK_MODE_KEY] = isDark
}
}
fun readThemePreference(context: Context): Flow<Boolean> =
context.dataStore.data.map { prefs -> prefs[DARK_MODE_KEY] ?: false }
🧠 ViewModel Integration
kotlinCopyEditclass ThemeViewModel(context: Context) : ViewModel() {
private val _isDarkMode = MutableStateFlow(false)
val isDarkMode: StateFlow<Boolean> = _isDarkMode
init {
viewModelScope.launch {
readThemePreference(context).collect {
_isDarkMode.value = it
}
}
}
fun toggleTheme(context: Context) {
viewModelScope.launch {
saveThemePreference(context, !_isDarkMode.value)
}
}
}

🧩 Best Practices for Dark Mode in Android
✅ Tip | 🔍 Description |
---|---|
Use isSystemInDarkTheme() | Respect system preferences for accessibility |
Avoid pure black/white | Use #121212 or Material Design colors |
Test your UI in both modes | Catch layout or contrast issues |
Use adaptive icons and images | Provide versions suitable for dark backgrounds |
Stick to Material You guidelines | Helps maintain visual consistency |
✅ Key Points – Dark Mode in Jetpack Compose
- Importance of Dark Mode
- Enhances visual ergonomics and battery life
- Improves accessibility for light-sensitive users
- Jetpack Compose Theming Basics
- Uses
MaterialTheme
,colors
,shapes
, andtypography
- Separates light and dark color palettes
- Uses
- Detect System Dark Mode
- Use
isSystemInDarkTheme()
to auto-detect system preference - Enables app to follow the device-wide theme setting
- Use
- Manually Toggle Dark Mode
- Implement user-controlled toggle via Switch or Button
- Provide override option for users who want manual control
- Store Theme Preference with DataStore
- Persist theme preference locally using
DataStore
- Retrieve and apply theme on app startup
- Persist theme preference locally using
- ViewModel for Theme Management
- Use ViewModel to expose theme state via
StateFlow
- Keeps UI reactive and lifecycle-aware
- Use ViewModel to expose theme state via
- Composable Previews for Light & Dark Modes
- Use
@Preview
annotations to check both themes during development - Improves design consistency
- Use
- Avoid Hardcoded Colors
- Always use
MaterialTheme.colors
- Ensures consistent styling in both modes
- Always use
- Test UI in Both Modes
- Validate contrast, visibility, and brand colors in dark/light modes
- Ensure components adapt properly
- SEO Tip for Developers
- Add theme support keywords like
Jetpack Compose Dark Mode
,Theme switching
,MaterialTheme
, etc., in content and image ALT tags for better visibility
- Add theme support keywords like
🔗 Internal Links for Contextual Learning
- 🔄 Day 24: Saving Preferences with DataStore
- 💡 Day 15: Navigation in Android
- 🧱 Day 13: Handling User Input
❓ Frequently Asked Questions
Use the MaterialTheme and isSystemInDarkTheme() functions to automatically apply dark or light themes based on system settings or user preferences.
You can use Jetpack DataStore to save the user’s dark mode preference across app launches.
Use Jetpack DataStore to persist dark mode preferences using a booleanPreferencesKey and flow-based API for reading the saved values.
Yes, on OLED displays, dark mode reduces power consumption by turning off unused pixels, improving battery life.
🧾 Conclusion
Adding Dark Mode support in Android using Jetpack Compose is no longer a luxury—it’s a user expectation. With Jetpack Compose’s declarative design, theme switching becomes easier, cleaner, and more scalable than ever before. By leveraging MaterialTheme, isSystemInDarkTheme(), and Jetpack DataStore, you can provide users with a seamless experience that respects system settings and personal preferences.
From creating light/dark color schemes to implementing a toggle switch and persisting state using ViewModel + DataStore, this guide covered a complete, production-ready solution.
Whether you’re building a small app or a large-scale product, Dark Mode support improves user satisfaction, accessibility, and energy efficiency.
Start implementing it today and make your Android apps visually adaptive, polished, and modern.