Text and typography
Every parameter of the Text composable that matters, why text sizes use sp and never dp, how the Material 3 type scale keeps a whole app consistent, and how to style one word inside a sentence.
The composable you will write most often
Text is the most-used composable in Android, by a distance. You have already used it a dozen times in the plainest possible way:
Text(text = "DICE DUEL")That gives you 16sp of near-black in the phone's default face. Perfectly readable, and completely flat — a screen made only of that looks like a spreadsheet, because nothing tells the eye where to start.
This lesson is about the parameters that fix it.
Look at a newspaper front page from across a room, too far away to read a word of it.
You still know exactly what is going on. There is one huge headline, a smaller line under it, a grey block of body text, and a tiny italic caption under the photograph. You have not read a single letter, yet you already know the order to read them in.
That is typography doing its job: size, weight and colour tell you what matters before you read anything. A screen with one text style makes every reader do that sorting work themselves, every time.
The parameters worth knowing
1Text(
2 text = "DICE DUEL",
3 color = Color(0xFF5B3FD6),
4 fontSize = 40.sp,
5 fontWeight = FontWeight.Black,
6 letterSpacing = 6.sp
7)| Parameter | What it does |
|---|---|
text | The characters. A String — numbers must be converted |
color | The colour of the letters themselves |
fontSize | Size, in sp |
fontWeight | Normal, Medium, SemiBold, Bold, Black |
fontStyle | FontStyle.Italic |
letterSpacing | Extra gap between characters, in sp |
lineHeight | Distance between lines of a wrapped paragraph |
textAlign | TextAlign.Center, .Start, .End, .Justify |
maxLines | Stop after this many lines |
overflow | What to do with text that does not fit |
textDecoration | TextDecoration.Underline, .LineThrough |
style | A whole bundle of the above, reused (see below) |
Two of those have traps in them, so they get their own sections.
sp, never dp, for text
Sizes on screen use . Text uses , and the difference is not cosmetic.
sp means scale-independent pixel. It starts out identical to dp, but it also multiplies by whatever text size the user chose in their phone's accessibility settings. Someone who has set their font to Large gets larger text in your app, automatically, without you writing anything.
Set your text in dp and you have quietly overruled that person's eyesight. It is one of the easiest failures to commit and one of the easiest to avoid: if it is text, it is sp.
1fontSize = 15.sp // right
2fontSize = 15.dp // wrong, and will not compileletterSpacing and lineHeight take sp too, for the same reason — they have to grow with the letters or the line falls apart.
overflow: what happens when it does not fit
By default, long text wraps onto as many lines as it needs, and if there is no room it is simply clipped mid-letter. That looks broken. The standard fix is two parameters together:
1Text(
2 text = note.title,
3 maxLines = 1,
4 overflow = TextOverflow.Ellipsis
5)maxLines = 1 stops it wrapping; TextOverflow.Ellipsis puts a … at the cut. Neither works properly without the other — overflow has nothing to do unless something is limiting the lines. You will use this pair constantly in Pocket Notes, where note titles are whatever length the user felt like.
The type scale: stop inventing numbers
Setting fontSize by hand on every Text works, and it does not scale. Twenty screens later your headings are 40, 38, 40 and 42sp, and nobody knows which is right.
ships a type scale: fifteen named, pre-designed text styles, sitting in your . You pick a role, not a number:
1Text(
2 text = "DICE DUEL",
3 style = MaterialTheme.typography.displaySmall
4)
5Text(
6 text = "First to 30 wins the duel",
7 style = MaterialTheme.typography.bodyMedium
8)The five families, biggest to smallest, each in Large, Medium and Small:
| Family | Use it for |
|---|---|
display* | One enormous number or word. A timer, a score |
headline* | The title of a screen |
title* | The title of a section or a card |
body* | Paragraphs and normal reading text |
label* | Buttons, chips, small captions in CAPS |
Text with no style uses bodyLarge, which is why plain text comes out at 16sp.
To bend one of them without abandoning the scale, use .copy():
1Text(
2 text = "PLAYER 1",
3 style = MaterialTheme.typography.labelLarge.copy(
4 letterSpacing = 2.sp
5 )
6)You get the scale's size, weight and line height, with one thing changed. Lesson 3.11 shows how to replace the whole scale with your own fonts.
Any parameter you pass directly to Text wins over the same setting inside style. So style = ...titleLarge, color = Color.Red is legal and does what you expect. Direct parameters are for one-offs; style is for the pattern you use everywhere.
A real title block
This is Dice Duel's, with the theme colours written out as literals for now — Lesson 3.11 replaces them with proper roles.
1@Composable
2fun TitleBlock() {
3 Column {
4 Text(
5 text = "DICE DUEL",
6 color = Color(0xFF5B3FD6),
7 fontSize = 40.sp,
8 fontWeight = FontWeight.Black,
9 letterSpacing = 6.sp
10 )
11 Spacer(Modifier.height(2.dp))
12 Text(
13 text = "First to 30 wins the duel",
14 color = Color(0xFF4A4463),
15 fontSize = 15.sp,
16 fontWeight = FontWeight.Medium,
17 letterSpacing = 1.sp
18 )
19 }
20}TitleBlock() — one composable, two Texts, a clear hierarchy.
Styling one word inside a sentence
Sometimes you need part of a line to look different — a bold number in a sentence, a coloured name. You cannot do it with two Text composables, because they would not wrap together as one paragraph.
The answer is an annotated string:
1val line = buildAnnotatedString {
2 append("First to ")
3 withStyle(
4 SpanStyle(fontWeight = FontWeight.Black)
5 ) {
6 append("30")
7 }
8 append(" wins")
9}
10
11Text(text = line)buildAnnotatedString gives you a little builder: append adds plain text, and withStyle applies a SpanStyle to everything appended inside it. The result is one piece of text that wraps, aligns and truncates as a single unit.
You will use this properly in Pocket Notes to highlight the matched part of a search result.
- Open ComposeLab and replace the contents of
setContentwith a call toTitleBlock(). - Type the
TitleBlockcomposable from this lesson. Accept the imports forandroidx.compose.ui.unit.sp,androidx.compose.ui.text.font.FontWeightandandroidx.compose.ui.graphics.Color. - Tap Run. You should see a violet logo and a grey subtitle.
- Change
letterSpacing = 6.spto0.spand Run. Same words, much cheaper-looking. - Change
fontSize = 40.spto40.dp. Try to build — the compiler stops you. This is the one mistake Compose will not let you make. - Now delete both
Textparameter lists down to justtext = ..., and addstyle = MaterialTheme.typography.displaySmallto the first andstyle = MaterialTheme.typography.bodyMediumto the second. Run. Different look, zero magic numbers. - Add a third
Textwith a very long sentence,maxLines = 1andoverflow = TextOverflow.Ellipsis. Run, and watch it end in a neat….
sp unit needs its own import, and it is in a different package from the Text composable itself.import androidx.compose.ui.unit.sp. It sits next to dp, in androidx.compose.ui.unit.fontSize = 40 instead of fontSize = 40.sp. Compose will not guess a unit for you..sp. The same applies to letterSpacing and lineHeight. If you accidentally wrote .dp you will get a similar mismatch — for text it is always sp.import androidx.compose.ui.text.font.FontWeight, then use FontWeight.Bold, FontWeight.Black and so on. For italics you also need androidx.compose.ui.text.font.FontStyle.Color(0x5B3FD6) is read as 0x005B3FD6 — fully transparent — because the first two digits are the opacity and you only supplied six digits in total.Color(0xFF5B3FD6). FF means fully opaque.maxLines will wrap, and if the container will not grow, the extra is clipped with no indication that anything is missing.maxLines = 1 and overflow = TextOverflow.Ellipsis. Import androidx.compose.ui.text.style.TextOverflow. Setting overflow on its own does nothing, because nothing is limiting the lines.Texttakescolor,fontSize,fontWeight,letterSpacing,lineHeight,textAlign,maxLinesandoverflow— and astylethat bundles them.- Text sizes are always , never , so the user's chosen font size is respected.
maxLinesplusoverflow = TextOverflow.Ellipsisis the standard way to handle text that is too long. Neither works alone.- Prefer
MaterialTheme.typography.*roles over hand-picked numbers, and.copy()when you need to bend one. - Colours need eight hex digits:
Color(0xFF5B3FD6). Six digits gives you invisible text. buildAnnotatedStringstyles part of a single piece of text.- Next: buttons — the trailing-lambda pattern that lets you put anything inside one, and where a tap should change state.