Dark theme has become an essential feature in modern mobile applications, offering improved battery life and reduced eye strain, particularly in low-light conditions. Jetpack Compose simplifies the implementation of dark themes, allowing developers to create visually appealing and user-friendly interfaces. This blog post explores how to define and implement dark theme color palettes in Jetpack Compose applications.
What is Dark Theme?
Dark theme, also known as night mode or dark mode, is a color scheme that uses dark backgrounds and light-colored text and UI elements. This reduces the amount of light emitted by the device screen, saving battery and reducing eye strain, especially in dark environments.
Why Implement Dark Theme?
- Battery Efficiency: OLED and AMOLED screens consume less power when displaying darker colors.
- Reduced Eye Strain: Easier on the eyes in low-light conditions.
- Accessibility: Enhances readability for users with visual impairments.
- User Preference: Many users prefer dark themes for aesthetic and practical reasons.
How to Implement Dark Theme Color Palettes in Jetpack Compose
Implementing dark theme color palettes involves defining distinct color schemes for light and dark modes, and then applying these themes in your Jetpack Compose application.
Step 1: Add Material3 Dependency
Ensure you have the Material3 dependency in your build.gradle
file:
dependencies {
implementation("androidx.compose.material3:material3:1.1.2")
}
Step 2: Define Color Palettes
Create color palettes for both light and dark themes using the ColorScheme
class in Jetpack Compose. These palettes define the colors used throughout your application’s UI.
import androidx.compose.ui.graphics.Color
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
// Light Theme Colors
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC5),
tertiary = Color(0xFF3700B3),
background = Color(0xFFFFFFFF),
surface = Color(0xFFFFFFFF),
onPrimary = Color.White,
onSecondary = Color.Black,
onTertiary = Color.White,
onBackground = Color.Black,
onSurface = Color.Black,
)
// Dark Theme Colors
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFFBB86FC),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF3700B3),
background = Color(0xFF121212),
surface = Color(0xFF121212),
onPrimary = Color.Black,
onSecondary = Color.Black,
onTertiary = Color.White,
onBackground = Color.White,
onSurface = Color.White,
)
Here:
LightColorScheme
defines the colors for the light theme.DarkColorScheme
defines the colors for the dark theme, using darker shades for the background and surface colors, and lighter shades for text and interactive elements.- The colors include primary, secondary, tertiary, background, surface, and corresponding “on” colors (e.g.,
onPrimary
,onBackground
), which represent the color of content displayed on top of these surfaces.
Step 3: Implement Theme Toggle Logic
To switch between light and dark themes, implement logic to detect the system’s current theme and update the application’s theme accordingly. You can use a mutableStateOf
variable to track the current theme.
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.Composable
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = remember(darkTheme) {
if (darkTheme) DarkColorScheme else LightColorScheme
}
}
Here:
- The
darkTheme
parameter usesisSystemInDarkTheme()
to determine whether the system is currently in dark mode. - The
remember
function caches thecolorScheme
based on thedarkTheme
value, ensuring the color scheme is updated only when the theme changes.
Step 4: Wrap Your Application in the Theme
Wrap your application content in the MaterialTheme
composable, passing in the appropriate colorScheme
.
import androidx.compose.material3.MaterialTheme
import androidx.compose.material.Surface
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = remember(darkTheme) {
if (darkTheme) DarkColorScheme else LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
Step 5: Use Theme Colors in Your UI
Use the MaterialTheme.colorScheme
object to access the theme colors in your composables. This ensures that your UI elements adapt to the selected theme.
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun MyScreen() {
Surface(color = MaterialTheme.colorScheme.background) {
Text(
text = "Hello, Dark Theme!",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(16.dp)
)
}
}
In this example:
- The
Surface
‘s background color is set toMaterialTheme.colorScheme.background
, which will be eitherLightColorScheme.background
orDarkColorScheme.background
depending on the current theme. - The text color is set to
MaterialTheme.colorScheme.onBackground
, ensuring it is readable against the background color.
Complete Example
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.unit.dp
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
// Light Theme Colors
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6200EE),
secondary = Color(0xFF03DAC5),
tertiary = Color(0xFF3700B3),
background = Color(0xFFFFFFFF),
surface = Color(0xFFFFFFFF),
onPrimary = Color.White,
onSecondary = Color.Black,
onTertiary = Color.White,
onBackground = Color.Black,
onSurface = Color.Black,
)
// Dark Theme Colors
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFFBB86FC),
secondary = Color(0xFF03DAC6),
tertiary = Color(0xFF3700B3),
background = Color(0xFF121212),
surface = Color(0xFF121212),
onPrimary = Color.Black,
onSecondary = Color.Black,
onTertiary = Color.White,
onBackground = Color.White,
onSurface = Color.White,
)
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = remember(darkTheme) {
if (darkTheme) DarkColorScheme else LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
@Composable
fun MyScreen() {
Surface(color = MaterialTheme.colorScheme.background) {
Text(
text = "Hello, Dark Theme!",
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(16.dp)
)
}
}
@Preview(showBackground = true)
@Composable
fun MyScreenPreview() {
MyAppTheme {
MyScreen()
}
}
@Preview(showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES)
@Composable
fun MyScreenDarkPreview() {
MyAppTheme(darkTheme = true) {
MyScreen()
}
}
Best Practices for Dark Theme Color Palettes
- Contrast Ratio: Ensure sufficient contrast between text and background colors to maintain readability.
- Consistency: Maintain consistency in color usage throughout the application to avoid jarring transitions.
- Testing: Thoroughly test your dark theme implementation on different devices and screen configurations to ensure optimal user experience.
Conclusion
Implementing dark theme color palettes in Jetpack Compose enhances the user experience by reducing eye strain and improving battery efficiency. By defining distinct color schemes for light and dark modes and utilizing MaterialTheme, you can create visually appealing and accessible applications that adapt to user preferences and system settings. By following the steps outlined in this blog post, you can seamlessly integrate dark theme support into your Jetpack Compose projects, making your apps more user-friendly and efficient.