Dark Theme in Jetpack Compose: Color Palettes and Implementation

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 uses isSystemInDarkTheme() to determine whether the system is currently in dark mode.
  • The remember function caches the colorScheme based on the darkTheme 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 to MaterialTheme.colorScheme.background, which will be either LightColorScheme.background or DarkColorScheme.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.