Work with fonts

This page describes how to set fonts in your Compose app.

Set font

Text has a fontFamily parameter to allow setting the font used in the composable. By default, serif, sans-serif, monospace and cursive font families are included:

@Composable
fun DifferentFonts() {
    Column {
        Text("Hello World", fontFamily = FontFamily.Serif)
        Text("Hello World", fontFamily = FontFamily.SansSerif)
    }
}

The words

You can use the fontFamily attribute to work with custom fonts and typefaces defined in the res/font folder:

Graphical depiction of the res > font folder in the development environment

This example shows how you would define a fontFamily based on those font files and using the Font function:

val firaSansFamily = FontFamily(
    Font(R.font.firasans_light, FontWeight.Light),
    Font(R.font.firasans_regular, FontWeight.Normal),
    Font(R.font.firasans_italic, FontWeight.Normal, FontStyle.Italic),
    Font(R.font.firasans_medium, FontWeight.Medium),
    Font(R.font.firasans_bold, FontWeight.Bold)
)

You can pass this fontFamily to your Text composable. Because a fontFamily can include different weights, you can manually set fontWeight to select the right weight for your text:

Column {
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Light)
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Normal)
    Text(
        text = "text",
        fontFamily = firaSansFamily,
        fontWeight = FontWeight.Normal,
        fontStyle = FontStyle.Italic
    )
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Medium)
    Text(text = "text", fontFamily = firaSansFamily, fontWeight = FontWeight.Bold)
}

The words

To learn how to set the typography in your entire app, see the Theming documentation.

Downloadable fonts

Starting in Compose 1.2.0, you can use the downloadable fonts API in your Compose app to download Google fonts asynchronously and use them in your app.

Support for downloadable fonts provided by custom providers is not currently available.

Use downloadable fonts programmatically

To download a font programmatically from within your app, follow these steps:

  1. Add the dependency:

    Groovy

    dependencies {
        ...
        implementation "androidx.compose.ui:ui-text-google-fonts:1.6.1"
    }
    

    Kotlin

    dependencies {
        ...
        implementation("androidx.compose.ui:ui-text-google-fonts:1.6.1")
    }
  2. Initialize the GoogleFont.Provider with the credentials for Google Fonts:
    val provider = GoogleFont.Provider(
        providerAuthority = "com.google.android.gms.fonts",
        providerPackage = "com.google.android.gms",
        certificates = R.array.com_google_android_gms_fonts_certs
    )
    The parameters the provider receives are:
    • The font provider authority for Google Fonts.
    • The font provider package to verify the identity of the provider.
    • A list of sets of hashes for the certificates to verify the identity of the provider. You can find the hashes required for the Google Fonts provider in the font_certs.xml file in the Jetchat sample app.
  3. Define a FontFamily:
    // ...
     import androidx.compose.ui.text.googlefonts.GoogleFont
     import androidx.compose.ui.text.font.FontFamily
     import androidx.compose.ui.text.googlefonts.Font
     // ...
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
        Font(googleFont = fontName, fontProvider = provider)
    )
    You can query for other parameters for your font like weight and style with FontWeight and FontStyle respectively:
    // ...
     import androidx.compose.ui.text.googlefonts.GoogleFont
     import androidx.compose.ui.text.font.FontFamily
     import androidx.compose.ui.text.googlefonts.Font
     // ...
    
    val fontName = GoogleFont("Lobster Two")
    
    val fontFamily = FontFamily(
        Font(
            googleFont = fontName,
            fontProvider = provider,
            weight = FontWeight.Bold,
            style = FontStyle.Italic
        )
    )
  4. Configure the FontFamily to be used in your Text composable function:

Text(
    fontFamily = fontFamily, text = "Hello World!"
)

You can also define Typography to use your FontFamily:

val MyTypography = Typography(
    labelMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.Normal, fontSize = 12.sp/*...*/
    ),
    labelLarge = TextStyle(
        fontFamily = fontFamily,
        fontWeight = FontWeight.Bold,
        letterSpacing = 2.sp,
        /*...*/
    ),
    displayMedium = TextStyle(
        fontFamily = fontFamily, fontWeight = FontWeight.SemiBold/*...*/
    ),
    /*...*/
)

Next, set the Typography to your app’s theme:

MyAppTheme(
    typography = MyTypography
)/*...*/

For an example of an app that’s implementing downloadable fonts in Compose together with Material3, see the Jetchat sample app.

Add fallback fonts

You can determine a chain of fallbacks for your font in case the font fails to download properly. For instance, if you have your downloadable font defined like this:

// ...
 import androidx.compose.ui.text.googlefonts.Font
 // ...

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
    Font(googleFont = fontName, fontProvider = provider),
    Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold)
)

You can define the defaults for your font for both weights like this:

// ...
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.googlefonts.Font
 // ...

val fontName = GoogleFont("Lobster Two")

val fontFamily = FontFamily(
    Font(googleFont = fontName, fontProvider = provider),
    Font(resId = R.font.my_font_regular),
    Font(googleFont = fontName, fontProvider = provider, weight = FontWeight.Bold),
    Font(resId = R.font.my_font_regular_bold, weight = FontWeight.Bold)
)

Make sure you’re adding the correct imports.

Defining the FontFamily like this creates a FontFamily containing two chains, one per weight. The loading mechanism will try to resolve the online font first, and then the font located in your local R.font resource folder.

Debug your implementation

To help you verify if the font is being downloaded correctly, you can define a debug coroutine handler. Your handle provides the behavior of what to do in case the font fails to load asynchronously.

Start by creating a CoroutineExceptionHandler:

val handler = CoroutineExceptionHandler { _, throwable ->
    // process the Throwable
    Log.e(TAG, "There has been an issue: ", throwable)
}

Pass it to the createFontFamilyResolver method to have the resolver use the new handler:

CompositionLocalProvider(
    LocalFontFamilyResolver provides createFontFamilyResolver(LocalContext.current, handler)
) {
    Column {
        Text(
            text = "Hello World!", style = MaterialTheme.typography.bodyMedium
        )
    }
}

You can also use the isAvailableOnDevice API from the provider to test if the provider is available and certificates are configured correctly. To do this, you can call the isAvailableOnDevice method that returns false if the provider is configured incorrectly.

val context = LocalContext.current
LaunchedEffect(Unit) {
    if (provider.isAvailableOnDevice(context)) {
        Log.d(TAG, "Success!")
    }
}

Caveats

Google Fonts takes several months to make new fonts available on Android. There's a gap in time between when a font is added in fonts.google.com and when it's available through the downloadable fonts API (either in the View system or in Compose). Newly added fonts might fail to load in your app with an IllegalStateException. To help developers identify this error over other types of font loading errors, we added descriptive messaging for the exception in Compose with the changes here. If you find any issues, report them using the issue tracker.

Use variable fonts

A variable font is a font format that allows one font file to contain different styles. With variable fonts, you can modify axes (or parameters) to generate your preferred style. These axes can be standard, such as weight, width, slant, and italic, or custom, which differ across variable fonts.

Five configurations of the same variable font with different axis values.
Figure 1. Text using the same variable font customized with different axis values.

Using variable fonts instead of regular font files allows you to only have one font file instead of multiple.

For more background about variable fonts, see Google Fonts Knowledge, the entire catalog of available variable fonts, and a table of the supported axes for each font.

This doc shows you how to implement a variable font in your Compose app.

Load a variable font

  1. Download the variable font you want to use (for example Roboto Flex) and place it in the app/res/font folder in your app. Make sure that the .ttf file you add is the variable font version of the font, and that the name of your font file is all lowercase and doesn't contain any special characters.

  2. To load a variable font, define a FontFamily using the font placed in the res/font/ directory:

    // In Typography.kt
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily =
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(950),
                    FontVariation.width(30f),
                    FontVariation.slant(-6f),
                )
            )
        )

    The FontVariation API allows you to configure standard font axes such as weight, width, and slant. These are standard axes that are available with any variable font. You can create different configurations of the font based on where the font will be used.

  3. Variable fonts are only available for Android versions O and above, so add a guardrail and configure an appropriate fallback:

    // In Typography.kt
    val default = FontFamily(
        /*
        * This can be any font that makes sense
        */
        Font(
            R.font.robotoflex_static_regular
        )
    )
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(950),
                    FontVariation.width(30f),
                    FontVariation.slant(-6f),
                )
            )
        )
    } else {
        default
    }

  4. Extract the settings into a set of constants for easier reuse and replace the font settings with these constants:

    // VariableFontDimension.kt
    object DisplayLargeVFConfig {
        const val WEIGHT = 950
        const val WIDTH = 30f
        const val SLANT = -6f
        const val ASCENDER_HEIGHT = 800f
        const val COUNTER_WIDTH = 500
    }
    
    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(DisplayLargeVFConfig.WEIGHT),
                    FontVariation.width(DisplayLargeVFConfig.WIDTH),
                    FontVariation.slant(DisplayLargeVFConfig.SLANT),
                )
            )
        )
    } else {
        default
    }

  5. Configure the Material Design 3 typography to use the FontFamily:

    // Type.kt
    val Typography = Typography(
        displayLarge = TextStyle(
            fontFamily = displayLargeFontFamily,
            fontSize = 50.sp,
            lineHeight = 64.sp,
            letterSpacing = 0.sp,
            /***/
        )
    )

    This sample uses displayLarge Material 3 typography, which has different default font settings and recommended uses. For example, you should use displayLarge for short, critical text, as it's the largest text on the screen.

    With Material 3, you can change the default values of TextStyle and fontFamily to customize your typography. In the snippet above, you configure instances of TextStyle to customize font settings for each font family.

  6. Now that you've defined your typography, pass it to the M3 MaterialTheme:

    MaterialTheme(
        colorScheme = MaterialTheme.colorScheme,
        typography = Typography,
        content = content
    )

  7. Finally, use a Text composable and specify the style to one of the defined typography styles, MaterialTheme.typography.displayLarge:

    @Composable
    @Preview
    fun CardDetails() {
        MyCustomTheme {
            Card(
                shape = RoundedCornerShape(8.dp),
                elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                Column(
                    modifier = Modifier.padding(16.dp)
                ) {
                    Text(
                        text = "Compose",
                        style = MaterialTheme.typography.displayLarge,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 1
                    )
                    Text(
                        text = "Beautiful UIs on Android",
                        style = MaterialTheme.typography.headlineMedium,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 2
                    )
                    Text(
                        text = "Jetpack Compose is Android’s recommended modern toolkit for building native UI. It simplifies and accelerates UI development on Android. Quickly bring your app to life with less code, powerful tools, and intuitive Kotlin APIs.",
                        style = MaterialTheme.typography.bodyLarge,
                        modifier = Modifier.padding(bottom = 8.dp),
                        maxLines = 3
                    )
                }
            }
        }
    }

    Each Text composable is configured through its Material theme's style and contains a different variable font configuration. You can use MaterialTheme.typography to retrieve the typography provided to the M3 MaterialTheme composable.

Three different texts, all showcasing different font configurations.
Figure 2. Variable font applied in three different configurations.

Use custom axes

Fonts can also have custom axes. These are defined within the font file itself. For example, the Roboto Flex font has the ascender height ("YTAS") axis, which adjusts the height of the lowercase ascenders, while counter width ("XTRA") adjusts the width of each letter.

You can change the value of these axes with the FontVariation settings.

For more information about the custom axes you can configure for a font, see the table of the supported axes for each font.

  1. To use custom axes, define functions for the custom ascenderHeight and counterWidth axes:

    fun ascenderHeight(ascenderHeight: Float): FontVariation.Setting {
        require(ascenderHeight in 649f..854f) { "'Ascender Height' must be in 649f..854f" }
        return FontVariation.Setting("YTAS", ascenderHeight)
    }
    
    fun counterWidth(counterWidth: Int): FontVariation.Setting {
        require(counterWidth in 323..603) { "'Counter width' must be in 323..603" }
        return FontVariation.Setting("XTRA", counterWidth.toFloat())
    }

    These functions do the following:

    • Define guardrails for the values they can accept. As you can see in the Variable fonts catalog, ascenderHeight (YTAS) has a minimum value of 649f and a maximum of 854f.
    • Return the font setting, so the configuration is ready to add to the font. In the FontVariation.Setting() method, the axis name (YTAS, XTRA) is hardcoded, and it takes the value as a parameter.
  2. Using the axes with the font configuration, pass additional parameters to each Font that is loaded:

    @OptIn(ExperimentalTextApi::class)
    val displayLargeFontFamily = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        FontFamily(
            Font(
                R.font.robotoflex_variable,
                variationSettings = FontVariation.Settings(
                    FontVariation.weight(DisplayLargeVFConfig.WEIGHT),
                    FontVariation.width(DisplayLargeVFConfig.WIDTH),
                    FontVariation.slant(DisplayLargeVFConfig.SLANT),
                    ascenderHeight(DisplayLargeVFConfig.ASCENDER_HEIGHT),
                    counterWidth(DisplayLargeVFConfig.COUNTER_WIDTH)
                )
            )
        )
    } else {
        default
    }

    Notice that the height of the lowercase ascenders is now increased, and the other text is wider:

Three different texts showing different configurations for variable fonts, with custom axes set— some have higher lowercase ascenders and are wider than before.
Figure 3. Text showing custom axes set on variable fonts.

Additional resources

For more information, see the following blog post about variable fonts: