State of Compose 2023 results are in! Click here to learn more
Published on

🚀 All about theming in Jetpack Compose

Authors

Heya! Happy Thursday. Alex here.

Today's tutorial is going to cover how to use themes in your Jetpack Compose app, using Material 3 compose.

You can still use this tutorial with Material compose. What's different is that they use different theme attributes (such as colors instead of colorScheme) and different imports, but the main concept is the same.

For a tl;dr what's different between Material and Material 3, checkout this Twitter thread.

Thinking in Themes

It all starts with the MaterialTheme() composable. This composable is responsible for setting up the look and feel for all the content of its children.

Most of the times, it is going to be the top level composable of your app:

MaterialTheme {
    HomeScreen()
}

out of the box, the MaterialTheme() composable gives you 3 different attributes which you can pass as parameters:

@Composable
fun MaterialTheme(
    colorScheme: ColorScheme = MaterialTheme.colorScheme,
    shapes: Shapes = MaterialTheme.shapes,
    typography: Typography = MaterialTheme.typography,
    content: @Composable () -> Unit
)

The parameters that you pass here are going to be used in any child composable passed to the content.

Example of this are ModalBottomSheets that were recently introduced in Material 3 compose. The value of the extraLarge parameter of the passing Shapes object will affect how the bottom sheet is going to look like. More on that in a bit.

The cool thing is MaterialTheme()'s children composables will have access to any parameter passed here via the MaterialTheme object. At any point that you need to access, say, the specified Typography, you can use the MaterialTheme object.

Here is a quick example:

MaterialTheme(
    typography = Typography(
        headlineLarge = TextStyle(
            fontSize = 24.sp
        )
    )
) {
    HomeScreen()
}

and here is how to access the headlineLarge property from any child:

@Composable
fun HomeScreen() {
    Text("This is my title", style = MaterialTheme.typography.headlineLarge)
}

you can use the MaterialTheme object to get any color, type style or shape you need, as long as the composable is wrapped with the MaterialTheme composable.

In other words: the MaterialTheme() composable gives access to the MaterialTheme object to all of its children and its children children.

By default, most (if not all) composables that come out of the box with Material 3 will use this object for default values. This means that updating the values passed to your theme will update the look and feel of your app.

Every MaterialTheme property explained

The 3 different attributes you can modify in your MaterialTheme (colorScheme, shapes, typography) are an implementation of how the Material 3 design system works.

Keep reading to learn how each attribute works and how it affects the looks of your app:

What is colorsScheme and how it works in Material 3 compose

Material 3 comes with a list of predefined (named) colors. The main ones are: primary, surface and background.

Primary is the main highlight of your app (for things like button backgrounds and other interactive elements). Surface sets the color for surfaces such as Cards and Bottom Sheets (a reminder that a surface is a piece of paper in the Material metaphor). Background is, well, the background of your app.

Every color comes with its pair. For each color in the colorScheme you will find a on_ counterpart. For example primary comes with onPrimary. The onPrimary is used for elements that are placed on top of other elements using the primary color.

A good example of this are buttons. They use primary as their background color. Their text uses the onPrimary color.

This works for any color in Material. Need to change the color of text that is used on top of a card? Update the onSurface color and the text will magically update to the given onSurface color.

Do not create ColorScheme() via its constructor

Instead of defining a ColorScheme from scratch (which requires a ton of constructor arguments) use one of the color scheme functions instead. Those are used as a base to definine your own themes. Those include lightScheme() (a base for light themes), darkScheme() (a base for dark themes).

dynamicLightColorScheme() and dynamicDarkColorScheme() are also available. These color schemes will create dynamic colors according to the user's wallpaper.

What is typography and how it works in Material 3 compose

Typography defines the styling of text in your app. All text styles are split in display, headline, title, body, and label. Each category comes in different size variations: small, medium and large.

Each combination is a TextStyle. Each TextStyle can contain anything related to the looks of text. This include the text fontSize, fontWeight, fontFamily, lineHeight to name a few.

The type of TextStyle your Text() composable is going to receive depends on the composable that it is wrapped in. For example, the TopAppBar() title uses the titleLarge textStyle.

What is shapes and how it works in Material 3 compose

Shape defines the looks (mostly corners) of different elements in your app. This affects composables such as buttons shapes, card and sheets.

Jetpack Compose comes with some predefined shapes you can use depending on your needs such as CircleShape and RectangleShape.

If you need rounded corners, checkout the RoundedCornerShape() function. CutCornerShape() is also available.

How to tell how a composable will be visually affected if I change my theme?

Check the default values of the composable you are interested in. Let's find out which theme value will change the background color of our Button()s.

Notice the colors default value of the Button() composable:

fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    // ... other parameters
)

The ButtonDefaults.buttonColors() function is the place where the Button() receives all of its colors.

If you check the source code of the Button() you will The background color of the button is called containerColor

fun buttonColors(
    containerColor: Color = FilledButtonTokens.ContainerColor.toColor(),
    // other parameters
)

If you check the ContainerColor you will notice its value is ColorSchemeKeyTokens.Primary. Digging deeper you will find this value is mapped to MaterialTheme.colorScheme.primary color.

Figuring out which value is affected is a bit tricky. You wouldn't need to do this often though as the composables that come out of the box should look and consistent according to your theme.


In this tutorial you learnt how to theme your app using Material 3 compose library. You learnt about the MaterialTheme() composable and how it grants access to the MaterialTheme object to all of its children (and their children). You learnt about the different attributes you can customize: colorScheme, typography and shapes.


⚡️ Breaking Jetpack Compose news

Jetpack Compose 1.4.0 was released yesterday along with a few others!

Here are all the new Jetpack Compose updates we got:

  • 🚀 Compose UI 1.4.0 (changelog)
  • 🚀 Compose UI 1.5.0-alpha01 (changelog)
  • 🎬 Compose Animation 1.4.0 (changelog)
  • 🎬 Compose Animation 1.5.0-alpha01 (changelog)
  • 🧰 Compose Compiler 1.4.4 (changelog)
  • 🧱 Compose Foundation 1.4.0 (changelog)
  • 🧱 Compose Foundation 1.5.0-alpha01 (changelog)
  • 🎨 Compose Material 1.4.0 (changelog)
  • 🎨 Compose Material 1.5.0-alpha01 (changelog)
  • 🎨 Compose Material 3 1.1.0-beta01 (changelog)
  • 🏃‍♂️ Compose Runtime 1.4.0 (changelog)
  • 🏃‍♂️ Compose Runtime 1.5.0-alpha01 (changelog)
  • ⌚️ Wear Compose 1.2.0-alpha07 (changelog)
  • ⌚️🎨 Wear Compose Material3 material3-1.0.0-alpha01 (changelog)

That's all I got for you today.

How did you find this issue? Let me know by replying to this email if you are reading in your inbox.

Until the next one,

Alex