Custom Themes

In this guide, you will learn how to create fully custom themes and how to use them to maintain consistent styling in your Compose apps.

Create a theme

To create a theme, use the buildTheme { } function.

val MyTheme = buildTheme { }

It returns a theming @Composable that you can use to wrap your app with:

@Composable
fun App() {
    MyTheme {
        Box(Modifier.fillMaxSize()) {
            Text("My awesome app")
        }
    }
}

The theming function we just created forwards any styling properties to its children. Children can access those properties using the Theme object. Those are usually colors, typography, shapes and anything you need to style your apps with.

But we haven't defined any, so let's do that next:

Customize your theme

Let's define some colors. To do this, let's create a colors property that will hold some color tokens:

val colors = ThemeProperty<Color>("colors")
val background = ThemeToken<Color>("background")
val onBackground = ThemeToken<Color>("on_background")

val MyTheme = buildTheme {
    properties[colors] = mapOf(
        background to Color(0xFFFAFAFA),
        onBackground to Color(0XFF0C0A09),
    )
}

Compose Unstyled does not force the structure of your themes and does not come with default styling options that you will end up removing afterwards.

You can create any kind of properties you need that fit your design needs.

Even though buildTheme is not a @Composable function, the scope it provides for defining your properties is. This is handy for when you need to load properties asynchronously without blocking your app (such as loading fonts) and creating dynamic themes.

Styling your app using your theme

We can now style our app using the Theme object to access the values for each token:

@Composable
fun App() {
    MyTheme {
        Box(Modifier.fillMaxSize().background(Theme[colors][background])) {
            Text("My awesome app", color = Theme[colors][onBackground])
        }
    }
}

And that's the gist. You now know how to create custom themes, customize them with the properties you need, and use them in your app.

Default properties

Compose Unstyled themes come with some default properties you can set to style your app.

Content Color

Sets the default Color used by all Unstyled components to render their content.

val MyTheme = buildTheme {
    defaultContentColor = Color(0XFF0C0A09)
}

Text Style

Sets the default TextStyle used by Text and TextField components to render their text contents.

val MyTheme = buildTheme {
    defaultTextStyle = TextStyle(
        fontWeight = FontWeight.Medium,
        fontSize = 16.sp
    )
}

Indication

Sets the default Indication used by interactive elements such as buttons and clickables. This is the same as providing an Indication value for the LocalIndication.

import com.composeunstyled.theme.rememberColoredIndication

val MyTheme = buildTheme {
    defaultIndication = rememberColoredIndication(
        hoveredColor = Color.White.copy(alpha = 0.3f),
        pressedColor = Color.White.copy(alpha = 0.5f),
        focusedColor = Color.Black.copy(alpha = 0.1f),
    )
}

TextSelectionColors

Controls the colors used by text and text fields for text selection. This is the same as providing a TextSelectionColors value for the LocalTextSelectionColors.

val MyTheme = buildTheme {
    defaultTextSelectionColors = TextSelectionColors(
        handleColor = Color.Blue,
        backgroundColor = Color.Blue.copy(alpha = 0.4f)
    )
}

Debugging your theme

Unstyled will throw an exception when you try to access a token that is not present in the current theme.

To make it simpler to debug such scenarios, it is highly recommended to name your themes when you create them.

By doing so, Unstyled will provide descriptive error messages when you try to access a token that does not exist during runtime.

val LightTheme = buildTheme {
    name = "LightTheme"
}