Compete in the Jetpack Compose #AndroidDevChallenge for a chance to win one of over 1,000 prizes, including a Google Pixel 5. Learn more

Text in Compose

Text is a central piece of any UI, and Jetpack Compose makes it easier to display or write text. Compose leverages composition of its building blocks, meaning you don’t need to overwrite properties and methods or extend big classes to have a specific composable design and logic working the way you want.

As its base, Compose provides a BasicText and BasicTextField which are the barebones to display text and handle user input. At a higher level, Compose provides Text and TextField, which are composables following Material Design guidelines. It’s recommended to use them as they have the right look and feel for users on Android, and includes other options to simplify their customization without having to write a lot of code.

Displaying text

The most basic way to display text is to use the Text composable with a String as an argument:

@Composable
fun SimpleText() {
  Text("Hello World")
}

The words "Hello World" in plain black text

Display text from resource

We recommend you use string resources instead of hardcoding Text values, as you can share the same strings with your Android Views as well as preparing your app for internationalization:

@Composable
fun StringResourceText() {
  Text(stringResource(R.string.hello_world))
}

Styling text

The Text composable has multiple optional parameters to style its content. Below, we’ve listed parameters that cover most common use cases with text. To see all the parameters of Text, we recommend you to look at the Compose Text source code.

Whenever you set one of these parameters, you’re applying the style to the whole text value. If you need to apply multiple styles within the same line or paragraphs, have a look at the section on multiple inline styles.

Changing the text color

@Composable
fun BlueText() {
  Text("Hello World", color = Color.Blue)
}

The words "Hello World" in blue text

Changing the text size

@Composable
fun BigText() {
  Text("Hello World", fontSize = 30.sp)
}

The words "Hello World" in a larger type size

Making the text italic

@Composable
fun ItalicText() {
  Text("Hello World", fontStyle = FontStyle.Italic)
}

The words "Hello World" in italic typestyle

Making the text bold

@Composable
fun BoldText() {
    Text("Hello World", fontWeight = FontWeight.Bold)
}

The words "Hello World" in bold typestyle

Text alignments

The textAlign parameter allows to set the alignment of the text within a Text composable surface area.

By default, Text will select the natural text alignment depending on its content value:

  • Left edge of the Text container for left-to-right alphabets such as Latin, Cyrillic, or Hangul
  • Right edge of the Text container for right-to-left alphabets such as Arabic or Hebrew
@Preview(showBackground = true)
@Composable
fun CenterText() {
    Text("Hello World", textAlign = TextAlign.Center,
                modifier = Modifier.width(150.dp))
}

The words "Hello World" centered in their containing element

If you want to set manually the text alignment of a Text composable, prefer using TextAlign.Start and TextAlign.End instead of TextAlign.Left and TextAlign.Right respectively, as they resolve to the right edge of the Text composable depending on the preferred language text orientation. For example, TextAlign.End aligns to the right side for French text and to the left side for Arabic text, but TextAlign.Right aligns to the right side no matter what alphabet is used.

Working with fonts

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 "Hello World" in two different fonts, with and without serifs

You can use the fontFamily attribute to work with custom fonts and typefaces defined in res/fonts 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:

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)
)

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

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

The words "Hello World" in several different type weights and styles

To learn how to set the typography in your entire app, look at the themes documentation.

Multiple styles in a text

To set different styles within the same Text composable, you have to use an AnnotatedString, a string that can be annotated with styles of arbitrary annotations.

AnnotatedString is a data class containing:

  • A Text value
  • A List of SpanStyleRange, equivalent to inline styling with position range within the text value
  • A List of ParagraphStyleRange, specifying text alignment, text direction, line height, and text indent styling

TextStyle is for use in the Text composable , whereas SpanStyle and ParagraphStyle is for use in AnnotatedString.

The difference between SpanStyle and ParagraphStyle is that ParagraphStyle can be applied to a whole paragraph, while SpanStyle can be applied at the character level. Once a portion of the text is marked with a ParagraphStyle, that portion is separated from the remaining as if it had line feeds at the beginning and end.

AnnotatedString has a type-safe builder to make it easier to create one:

@Composable
fun MultipleStylesInText() {
    Text(buildAnnotatedString {
        withStyle(style = SpanStyle(color = Color.Blue)) {
            append("H")
        }
        append("ello ")

        withStyle(style = SpanStyle(fontWeight = FontWeight.Bold, color = Color.Red)) {
            append("W")
        }
        append("orld")
    })
}

The words "Hello World" with several style changes inline; the H is blue, and the W is red and bold

We can set paragraph styles in the same way:

@Composable
fun ParagraphStyle() {
    Text(buildAnnotatedString {
        withStyle(style = ParagraphStyle(lineHeight = 30.sp)) {
            withStyle(style = SpanStyle(color = Color.Blue)) {
                append("Hello\n")
            }
            withStyle(style = SpanStyle(fontWeight = FontWeight.Bold,
                                        color = Color.Red)) {
                append("World\n")
            }
            append("Compose")
        }
    })
}

Three paragraphs in three different styles: Blue, red and bold, and plain black

Maximum number of lines

To limit the number of visible lines in a Text composable, set the maxLines parameter:

@Composable
fun LongText() {
    Text("hello ".repeat(50), maxLines = 2)
}

A long text passage truncated after two lines

Text overflow

When limiting a long text, you may want to indicate a text overflow, which is only shown if the displayed text is truncated. To do so, set the textOverflow parameter:

@Composable
fun OverflowedText() {
    Text("Hello Compose ".repeat(50), maxLines = 2, overflow = TextOverflow.Ellipsis)
}

A long passage of text truncated after three lines, with an ellipsis at the end

Theming

To use the app theme for text styling, see the themes documentation.

User interactions

Jetpack Compose enables fine-grained interactivity in Text. Text selection is now more flexible and can be done across composable layouts. User interactions in text are different from other composable layouts, as you can’t add a modifier to a portion of a Text composable. This section highlights the different APIs to enable user interactions.

Selecting text

By default, composables aren’t selectable, which means by default users can't select and copy text from your app. To enable text selection, you need to wrap your text elements with a SelectionContainer composable:

@Composable
fun SelectableText() {
    SelectionContainer {
        Text("This text is selectable")
    }
}

A short passage of text, selected by the user.

You may want to disable selection on specific parts of a selectable area. To do so, you need to wrap the unselectable part with a DisableSelection composable:

@Composable
fun PartiallySelectableText() {
    SelectionContainer {
        Column {
            Text("This text is selectable")
            Text("This one too")
            Text("This one as well")
            DisableSelection {
                Text("But not this one")
                Text("Neither this one")
            }
            Text("But again, you can select this one")
            Text("And this one too")
        }
    }
}

A longer passage of text. The user tried to select the whole passage, but since two lines had DisableSelection applied, they were not selected.

Getting the position of a click on text

To listen for clicks on Text, you can add the clickable modifier. However, if you’re looking to get the position of a click within a Text composable, in the case where you have different actions based on different parts of the text, you need to use a ClickableText instead:

@Composable
fun SimpleClickableText() {
    ClickableText(
        text = AnnotatedString("Click Me"),
        onClick = { offset ->
            Log.d("ClickableText", "$offset -th character is clicked.")
        }
    )
}

Click with annotation

When a user clicks on a Text composable, you may want to attach extra information to a part of the Text value, like a URL attached to a specific word to be opened in a browser for exemple. To do so, you need to attach an annotation, which takes a tag (String), an item (String), and a text range as parameters. From an AnnotatedString, these annotations can be filtered with their tags or text ranges. Here’s an example:

@Composable
fun AnnotatedClickableText() {
    val annotatedText = buildAnnotatedString {
        append("Click ")

        // We attach this *URL* annotation to the following content
        // until `pop()` is called
        pushStringAnnotation(tag = "URL",
                             annotation = "https://developer.android.com")
        withStyle(style = SpanStyle(color = Color.Blue,
                                    fontWeight = FontWeight.Bold)) {
            append("here")
        }

        pop()
    }

    ClickableText(
        text = annotatedText,
        onClick = { offset ->
            // We check if there is an *URL* annotation attached to the text
            // at the clicked position
            annotatedText.getStringAnnotations(tag = "URL", start = offset,
                                                    end = offset)
                .firstOrNull()?.let { annotation ->
                    // If yes, we log its value
                    Log.d("Clicked URL", annotation.item)
                }
        }
    )
}

Entering and modifying text

TextField allows users to enter and modify text. There are two levels of TextField implementations:

  1. TextField is the Material Design implementation. We recommend you choose this implementation as it follows material design guidelines:
    • Default styling is filled
    • OutlinedTextField is the outline styling version
  2. BasicTextField enables users to edit text via hardware or software keyboard, but provides no decorations like hint or placeholder.
@Composable
fun SimpleFilledTextFieldSample() {
    var text by remember { mutableStateOf("Hello") }

    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

An editable text field containing the word "Hello". The field has the non-editable label "label".

@Composable
fun SimpleOutlinedTextFieldSample() {
    var text by remember { mutableStateOf("") }

    OutlinedTextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("Label") }
    )
}

An editable text field, with a purple border and label.

Styling TextField

TextField and BasicTextField share a lot of common parameters to customize them. The complete list for TextField is available in the TextField source code. This is a non-exhaustive list of some of the useful parameters:

  • singleLine
  • maxLines
  • textStyle
@Composable
fun StyledTextField() {
    var value by remember { mutableStateOf("Hello\nWorld\nInvisible") }

    TextField(
        value = value,
        onValueChange = { value = it },
        label = { Text("Enter text") },
        maxLines = 2,
        textStyle = TextStyle(color = Color.Blue, fontWeight = FontWeight.Bold),
        modifier = Modifier.padding(20.dp)
    )
}

A multiline TextField, with two editable lines plus the label

We recommend TextField over BasicTextField when your design calls for a Material TextField or OutlineTextField. However, BasicTextField should be used when building designs that don't need the decorations from the Material spec.

Keyboard options

TextField lets you set keyboard configurations options, such as the keyboard layout, or enable the autocorrect if it’s supported by the keyboard. Some options may not be guaranteed if the software keyboard doesn't comply with the options provided here. Here is the list of the supported keyboard options:

  • capitalization
  • autoCorrect
  • keyboardType
  • imeAction

Formatting

TextField allows you to set a visual formatting to the input value, like replacing characters with * for passwords, or inserting hyphens every 4 digits for a credit card number:

@Composable
fun PasswordTextField() {
    var password by rememberSaveable { mutableStateOf("") }

    TextField(
        value = password,
        onValueChange = { password = it },
        label = { Text("Enter password") },
        visualTransformation = PasswordVisualTransformation(),
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
    )
}

A password text entry field, with the text masked

More examples are available in the VisualTransformSamples source code.