Google is committed to advancing racial equity for Black communities. See how.

Theming in Compose

Jetpack Compose makes it easy to give your app a consistent look and feel by applying themes. You can customize Compose's implementation of Material Design to fit your product's brand. If that doesn't suit your needs, you can build a custom design system using Compose's public APIs.

Application-wide theming

Jetpack Compose offers an implementation of Material Design, a comprehensive design system for creating digital interfaces. The Material Design components (buttons, cards, switches, and so on) are built on top of Material Theming, which is a systematic way to customize Material Design to better reflect your product’s brand. A Material Theme comprises color, typography and shape attributes. When you customize these attributes, your changes are automatically reflected in the components you use to build your app.

Jetpack Compose implements these concepts with the MaterialTheme composable:

MaterialTheme(
  colors = …,
  typography = …,
  shapes = …
) {
  // app content
}

Configure the parameters you pass to MaterialTheme to theme your application.

Two contrasting screenshots. The first uses default MaterialTheme styling,
the second screenshot uses modified styling.

Figure 1. The first screenshot shows an app that does not configure MaterialTheme, and so it uses default styling. The second screenshot shows an app that passes parameters to MaterialTheme to customize the styling.

Color

Colors are modelled in Compose with the Color class, a simple data holding class.

val red = Color(0xffff0000)
val blue = Color(red = 0f, green  0f, blue = 1f)

While you can organize these however you like (as top-level constants, within a singleton, or defined inline), we strongly recommend specifying colors in your theme and retrieving the colors from there. This approach makes it possible to support multiple themes, like dark theme.

Example of theme's color palette

Compose provides the Colors class to model the Material color system. Colors provides builder functions to create sets of light or dark colors:

private val yellow200 = Color(0xffffeb46)
private val blue200 = Color(0xff91a4fc)
…

private val DarkColors = darkColors(
  primary = yellow200,
  secondary = blue200,
  …
)
private val LightColors = lightColors(
  primary = yellow500,
  primaryVariant = yellow400,
  secondary = blue700,
  …
)

Once you have defined your Colors you can pass them to a MaterialTheme:

MaterialTheme(
  colors = if (darkTheme) DarkColors else LightColors,
  typography = …,
  shapes = …
) {
  // app content
}

Using theme colors

You can retrieve the Colors provided to the MaterialTheme composable by using MaterialTheme.colors.

Text(
  text = "Hello theming",
  color = MaterialTheme.colors.primary
)

Surface and content color

Many components accept a pair of color and "content color":

Surface(
  color: Color = MaterialTheme.colors.surface,
  contentColor: Color = contentColorFor(color),
  …

TopAppBar(
  backgroundColor: Color = MaterialTheme.colors.primarySurface,
  contentColor: Color = contentColorFor(backgroundColor),
…

This enables you to not only set the color of a composable, but also to provide a default color for the content, the composables contained within it. Many composables use this content color by default. For example, Text bases its color on its parent's content color, and Icon uses that color to set its tint.

Two examples of the same banner, with different colors

Figure 2. Setting different background colors produces different text and icon colors.

The contentColorFor() method retrieves the appropriate "on" color for any theme colors. For example, if you set a primary background, that sets onPrimary as the content color. If you set a non-theme background color, you should also specify a sensible content color. Use AmbientContentColor to retrieve the current content color which contrasts with the current background.

Emphasis

Emphasis allows you to visually show the relative importance of a component, by altering the alpha of AmbientContentColor. Setting emphasis makes it easy and consistent to set the importance of components by providing and using EmphasisLevels:

// Both Icon & Text use AmbientContentColor by default; ProvideEmphasis alters
// the alpha of AmbientContentColor
ProvideEmphasis(emphasis = AmbeintEmphasisLevels.current.high) {
    Text(…)
}
ProvideEmphasis(emphasis = AmbeintEmphasisLevels.current.medium) {
    Icon(…)
    Text(…)
}

Screenshot of an article title, showing different levels of text
emphasis

Figure 3. Apply different levels of emphasis to text to visually communicate the information hierarchy.

Dark theme

In Compose, you implement light and dark themes by providing different sets of Colors to the MaterialTheme composable, and consuming colors through the theme:

@Composable
fun MyTheme(
  darkTheme: Boolean = isSystemInDarkTheme(),
  content: @Composable () -> Unit
) {
  MaterialTheme(
    colors = if (darkTheme) DarkColors else LightColors,
    typography = …,
    shapes = …,
    content = content
  )
}

In this example, MaterialTheme is wrapped in its own composable function, which accepts a parameter that specifies whether to use a dark theme or not. In this case, the function gets the default value for darkTheme by querying the device theme setting.

When implementing a dark theme, you can check if the current Colors are light or dark:

val isLightTheme = MaterialTheme.colors.isLight

This value is set by the lightColors() and darkColors() builder functions.

In Material, surfaces in dark themes with higher elevations receive elevation overlays, which lightens their background. These overlays are implemented automatically by the Surface composable when using dark colors:

Surface(
  elevation = 2.dp,
  color = MaterialTheme.colors.surface, // color will adjusted for elevation
  …

Screenshot of an app, showing the subtly different colors used for elements
at different elevation levels

Figure 4. The Cards and Bottom Navigation are surface colored like the background, but because they are at a higher elevation, their color is slightly lighter.

Extending Material colors

Compose closely models Material’s color theming to make it simple and type-safe to follow material guidelines. If you need to extend the color set, you can either implement your own color system as shown below, or add extensions:

@Composable
val Colors.snackbarAction: Color
  get() = if (isLight) Red300 else Red700

Typography

Material defines a type system, encouraging you to use a small number of semantically-named styles.

Example of several different typefaces in various
styles

Compose implements the type system with the Typography, TextStyle, and font-related classes. The Typography constructor offers defaults for each style so you can omit any you don’t want to customize:

private val Rubik = fontFamily(
    font(R.font.rubik_regular),
    font(R.font.rubik_medium, FontWeight.W500),
    font(R.font.rubik_bold, FontWeight.Bold)
)

private val MyTypography = Typography(
  h1 = TextStyle(
    fontFamily = Rubik,
    fontWeight = FontWeight.W300,
    fontSize = 96.sp
  ),
  body1 = TextStyle(
    fontFamily = Rubik,
    fontWeight = FontWeight.W600,
    fontSize = 16.sp
  )
  …
)
MaterialTheme(typography = MyTypography, …)

If you want to use the same font throughout, specify the defaultFontFamily parameter and omit the fontFamily of any TextStyle elements:

val typography = Typography(defaultFontFamily = Rubik)
MaterialTheme(typography = typography, …)

Using text styles

Retrieve TextStyle from the theme, as shown in this example:

Text(
    text = "Subtitle2 styled"
    style = MaterialTheme.typography.subtitle2
)

Screenshot showing a mixture of different typefaces for different purposes

Figure 5. Use a selection of typefaces and styles to express your brand.

Shape

Material defines a shape system, allowing you to define shapes for large, medium, and small components.

Shows a variety of Material Design shapes

Compose implements the shape system with the Shapes class, which lets you specify a CornerBasedShape for each category:

val Shapes = Shapes(
  small = RoundedCornerShape(percent = 50),
  medium = RoundedCornerShape(0f),
  large = CutCornerShape(
    topLeft = 16.dp,
    topRight = 0.dp,
    bottomRight = 0.dp,
    bottomLeft = 16.dp
  )
)

MaterialTheme(shapes = Shapes, …)

Many components use these shapes by default. For example, Button, TextField, and FloatingActionButton default to small, AlertDialog defaults to medium, and ModalDrawerLayout defaults to large — see the shape scheme reference for the complete mapping.

Using shapes

Retrieve the shapes from the theme:

Surface(
  shape = MaterialTheme.shapes.medium …
) {
  ...
}

Screenshot of an app that uses Material shapes to convey what state an element
is in

Figure 6. Use shapes to express brand or state.

Component styles

There is no explicit concept of component styles in Compose, as you provide this functionality by creating your own composables. For example, to create a style of button, wrap a button in your own composable function, directly setting the parameters you wish to alter, and exposing others as parameters to the containing composable.

@Composable
fun LoginButton(
  onClick: () -> Unit,
  modifier: Modifier = Modifier,
  text: @Composable () -> Unit) {
  Button(
    backgroundColor = MaterialTheme.colors.secondary,
    onClick = onClick,
    modifier = modifier,
    text = text
  }
}

Custom design systems

While Material is our recommended design system and Jetpack Compose ships an implementation of Material, you are not restricted to using it. It’s entirely possible to create your own design system in the same manner; Material is built entirely on public APIs which you can use to achieve this.

A complete description of how to build a custom design system is beyond the scope of this document, but see the following resources:

Learn more

To learn more, try the Jetpack Compose Theming codelab.