Jetpack Compose: Color Palettes and Themes

In Jetpack Compose, theming plays a critical role in defining the visual identity and consistency of your application. Using color palettes and themes effectively can significantly enhance the user experience by ensuring a cohesive and visually appealing design. Jetpack Compose provides powerful tools to customize the color scheme, typography, and shapes used throughout your application.

Understanding Color Palettes in Jetpack Compose

A color palette is a set of colors that you define to use in your application’s user interface. Jetpack Compose provides a Color class that allows you to specify colors using different color models such as RGB, ARGB, and hexadecimal values. Material Design, which is heavily used in Android development, recommends having primary, secondary, background, surface, error, and other colors in your palette.

Why Use Color Palettes?

  • Consistency: Ensures that colors are consistent throughout the application.
  • Maintainability: Makes it easier to update and change colors across the app in one place.
  • Theming Support: Enables support for dark and light themes or custom themes.

Implementing Color Palettes in Jetpack Compose

To implement color palettes, you define a Colors object for light and dark themes, respectively.

Step 1: Define Color Constants

Define your colors using the Color class. It is a good practice to organize them as constants.


import androidx.compose.ui.graphics.Color

val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8BC)

val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

Step 2: Define Color Schemes

Create ColorScheme objects for light and dark themes. You can customize the primary, secondary, background, surface, and other colors to match your brand or design requirements.


import androidx.compose.material3.ColorScheme
import androidx.compose.ui.graphics.Color

val LightColorScheme = ColorScheme(
    primary = Purple40,
    secondary = PurpleGrey40,
    tertiary = Pink40,
    background = Color.White, // Default background color for light theme
    surface = Color.White,    // Default surface color for components in light theme
    onPrimary = Color.White,
    onSecondary = Color.Black,
    onTertiary = Color.Black,
    onBackground = Color.Black, // Default text color for light theme
    onSurface = Color.Black,    // Default text color on surfaces
)

val DarkColorScheme = ColorScheme(
    primary = Purple80,
    secondary = PurpleGrey80,
    tertiary = Pink80,
    background = Color.Black, // Default background color for dark theme
    surface = Color(0xFF303030),    // Darker surface color for dark theme
    onPrimary = Color.Black,
    onSecondary = Color.White,
    onTertiary = Color.White,
    onBackground = Color.White, // Default text color for dark theme
    onSurface = Color.White     // Default text color on surfaces
)

Implementing Themes in Jetpack Compose

Themes in Jetpack Compose allow you to apply consistent styling across your application. Using the MaterialTheme composable, you can set the color scheme, typography, and shapes.

Step 1: Create a Theme Composable

Create a composable function to wrap your application’s UI with the MaterialTheme. This allows you to set the color scheme and typography.


import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        content = content
    )
}

Here’s a more advanced and complete version incorporating dynamic color:


import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,  // Enable or disable dynamic color
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        content = content
    )
}

Step 2: Use the Theme in Your Application

Wrap your composable content with your custom theme to apply the defined color scheme.


import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Composable
fun MyApp() {
    MyAppTheme {
        Greeting(name = "Android")
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApp()
}

Customizing Typography

Jetpack Compose allows you to customize the typography in your application, defining font families, font weights, and font styles. You can create a Typography object and pass it to the MaterialTheme.

Step 1: Define Typography

Create a Typography object with your custom text styles.


import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

val CustomTypography = Typography(
    bodyLarge = TextStyle(
        fontFamily = FontFamily.Default,
        fontWeight = FontWeight.Normal,
        fontSize = 16.sp
    ),
    titleLarge = TextStyle(
        fontFamily = FontFamily.Serif,
        fontWeight = FontWeight.Bold,
        fontSize = 22.sp
    )
)

Step 2: Use Custom Typography in the Theme

Pass the Typography object to the MaterialTheme.


import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme

    MaterialTheme(
        colorScheme = colorScheme,
        typography = CustomTypography,
        content = content
    )
}

Customizing Shapes

You can also customize the shapes used in your application using the Shapes object. This includes defining the corner radius for different component types.

Step 1: Define Shapes

Create a Shapes object with your custom shape definitions.


import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp

val CustomShapes = Shapes(
    small = RoundedCornerShape(4.dp),
    medium = RoundedCornerShape(8.dp),
    large = RoundedCornerShape(12.dp)
)

Step 2: Use Custom Shapes in the Theme

Pass the Shapes object to the MaterialTheme.


import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme

    MaterialTheme(
        colorScheme = colorScheme,
        typography = CustomTypography,
        shapes = CustomShapes,
        content = content
    )
}

Dynamic Color (Material You)

Starting with Android 12, the Material You design system introduces dynamic color, which extracts colors from the user’s wallpaper to personalize the app’s color scheme.

Enabling Dynamic Color

To enable dynamic color in your app, use the dynamicLightColorScheme and dynamicDarkColorScheme functions. These functions are available from API level 31 (Android 12) onwards.


import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext

@Composable
fun MyAppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,  // Enable or disable dynamic color
    content: @Composable () -> Unit
) {
    val colorScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
            val context = LocalContext.current
            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
        }
        darkTheme -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        content = content
    )
}

Conclusion

Effective use of color palettes and themes is crucial for creating a consistent and visually appealing user interface in Jetpack Compose. By defining color schemes, customizing typography, and shaping elements, you can tailor your application’s appearance to match your brand and enhance the user experience. Dynamic color in Material You further personalizes the experience by adapting to the user’s wallpaper. Theming not only makes your app look better but also makes it easier to maintain and update its design.