skip to content

Text & Decoration

Text, TextStyle and Text.rich with TextSpan control inline typography, while BoxDecoration, ClipRRect and Card shape surfaces. Interviewers check overflow handling and Container versus DecoratedBox.

part ofFlutteroverview, primer and where to startread it →
on this pageshow

explore

questions

5

In Flutter, how do you keep a long Text from spilling out of its space, and what do maxLines, overflow and softWrap each control?

level: juniorimportance: must knowfreq 62%

answer

  1. wrapping needs a bounded width
  2. softWrap defaults to true
  3. maxLines caps the line count
  4. TextOverflow: clip, fade, ellipsis, visible
  5. defaults come from DefaultTextStyle

basics

~20 s

Give the Text a bounded width, then limit it with maxLines and choose how the cut is drawn with overflow (clip, fade, ellipsis or visible). softWrap decides whether text may break onto new lines at all; it defaults to true.

solid answer

~40 s

`Text` lays out inside the width its parent allows. `softWrap` (default `true` through `DefaultTextStyle`) lets it break at word boundaries; with `softWrap: false` it stays on one line. `maxLines` caps the number of lines, and `overflow` decides what happens to text beyond the limit: `TextOverflow.clip` (the default) cuts it, `fade` fades the end, `ellipsis` adds an ellipsis, and `visible` paints outside the box. So a coupon title is `Text(title, maxLines: 2, overflow: TextOverflow.ellipsis)`. None of this helps if the `Text` has unbounded width, as a direct child of a `Row` does; there it must be wrapped in `Expanded` or `Flexible` first. When `maxLines`, `overflow` or `softWrap` are omitted, `Text` takes them from the nearest `DefaultTextStyle`.

go deeper

for a junior

Recall the four TextOverflow values, that clip is the default, and that maxLines plus ellipsis is the usual way to truncate titles.

for a middle

Explain how softWrap, maxLines and overflow interact, where the defaults come from, and why a bounded width is required.

for a senior

Pick truncation deliberately per screen, avoid truncating essential content, and measure with TextPainter when UI depends on it.

for a principal

Set team conventions for truncation in shared components so text behaves the same across the app and across languages.

## How Text decides its lines A `Text` widget builds a paragraph and lays it out within the **maximum width** its parent allows. Given a finite width, it breaks lines at word boundaries until the text fits; its height is whatever those lines need. Three parameters then shape the result: - **`softWrap`** — whether line breaks at soft wrap points (spaces, hyphenation opportunities) are allowed. It defaults to `true` via `DefaultTextStyle`. With `false`, the text is laid out as if it had unlimited horizontal space, so it stays on one line and any excess is handled by `overflow`. - **`maxLines`** — an optional cap on the number of lines. Text beyond the last allowed line is handled by `overflow`. It must be greater than zero when given. - **`overflow`** — a `TextOverflow` value deciding how excess text is drawn. ## The four TextOverflow values | Value | Effect | Typical use | |---|---|---| | `TextOverflow.clip` | Cuts the text at the box edge | The default; fine when cropping is acceptable | | `TextOverflow.fade` | Fades the end to transparent | Single-line labels with `softWrap: false` | | `TextOverflow.ellipsis` | Replaces the tail with an ellipsis | Titles and list rows | | `TextOverflow.visible` | Paints outside its box | Rare; decorative text that may overlap | `overflow` can also come from `TextStyle.overflow`; the `Text` parameter wins, then the style, then `DefaultTextStyle.overflow`. ## Where the defaults come from `Text` resolves every omitted parameter from the nearest **`DefaultTextStyle`** ancestor: `softWrap`, `overflow`, `maxLines`, `textAlign` and the base `TextStyle`. `DefaultTextStyle.fallback()`, used when no ancestor exists, has `softWrap: true`, `overflow: TextOverflow.clip` and no `maxLines`. That is why a subtree wrapped in `DefaultTextStyle.merge(maxLines: 1, overflow: TextOverflow.ellipsis, ...)` truncates every `Text` inside it without touching each widget. ## The bounded-width prerequisite Truncation only works when the text knows how much space it has. The classic failure is a `Text` placed directly in a `Row`: a `Row` lets its non-flexible children be as wide as they like, so the text lays out at its natural width, `ellipsis` never triggers, and the `Row` reports an overflow with the yellow-and-black striped warning. Wrapping the `Text` in `Expanded` or `Flexible` gives it a bounded width, and then `maxLines` and `overflow` behave. The constraint rules behind that belong to layout, but the practical rule for text is simple: **no bounded width, no ellipsis**. ```dart import 'package:flutter/material.dart'; class CouponTitleRow extends StatelessWidget { const CouponTitleRow({super.key, required this.title, required this.badge}); final String title; final Widget badge; @override Widget build(BuildContext context) { return Row( children: <Widget>[ Expanded( child: Text( title, maxLines: 2, overflow: TextOverflow.ellipsis, ), ), const SizedBox(width: 8), badge, ], ); } } ``` ## Choosing values for real screens 1. **List rows and cards:** `maxLines: 1` or `2` with `TextOverflow.ellipsis`, so rows keep a stable height. 2. **Labels that must stay on one line:** `softWrap: false` with `TextOverflow.fade` or `ellipsis`. 3. **Body text:** no `maxLines`; let it wrap and let the surrounding scroll view handle length. 4. **Expandable text:** toggle `maxLines` between a number and `null` in state to implement "read more". ## Truncation and translations Strings that fit in English often do not fit in German or Finnish, and a two-line limit that looks generous in one language truncates meaning in another. When a title is capped with `maxLines`, check the longest translation, and prefer letting secondary text wrap over hiding it. Right-to-left languages need no special handling for the ellipsis: `Text` places it at the logical end of the line based on the ambient `Directionality`. ## Common mistakes - Setting `overflow: TextOverflow.ellipsis` without `maxLines` or `softWrap: false` and expecting a single line; with wrapping allowed and no line cap, the text simply wraps. - Expecting ellipsis inside a `Row` without `Expanded` or `Flexible`. - Using `visible` to "fix" clipping, which only draws text over neighbouring widgets. - Hardcoding a `SizedBox` height for multi-line text, which clips when the user's system text size is larger; `maxLines` is the robust cap. - Truncating text that carries essential meaning, such as a coupon's terms, instead of letting it wrap. ## Measuring before truncating Occasionally a screen needs to know whether truncation happened, for example to show a "more" button only when needed. The `Text` widget does not report that; the usual approach is a `LayoutBuilder` plus a `TextPainter` laid out with the same style, `maxLines` and width, and then reading `TextPainter.didExceedMaxLines`. Keep this for the few places that need it, since it lays the text out twice.

  • In Flutter, why does Text(title, overflow: TextOverflow.ellipsis) sometimes wrap onto several lines instead of showing an ellipsis?
    With `softWrap` true and no `maxLines`, text may wrap onto as many lines as it needs, so there is never excess to ellipsize. Add `maxLines: 1` (or `softWrap: false`) so the text has a limit to overflow, and make sure its width is bounded.
  • In Flutter, how would you implement a 'read more' toggle on a long coupon description?
    Keep a boolean in a `StatefulWidget` and pass `maxLines: expanded ? null : 3` with `overflow: TextOverflow.ellipsis`. To show the toggle only when the text is actually truncated, measure it with a `TextPainter` using the same style and width inside a `LayoutBuilder` and check `didExceedMaxLines`.

saying these in an interview costs you the question

  • Expecting ellipsis to work on a Text placed directly in a Row
  • Thinking overflow alone limits text to one line
  • Believing the default overflow is ellipsis
  • Using TextOverflow.visible to fix clipped text
  • Fixing text overflow with a hardcoded container height
open as a page

In Flutter, what is the difference between Container and DecoratedBox, and when would you choose each?

level: middleimportance: must knowfreq 55%

basics

~20 s

Container is a convenience widget that composes smaller widgets such as Padding, ColoredBox, DecoratedBox, ConstrainedBox, Align and Transform. DecoratedBox only paints a Decoration. Choose DecoratedBox or ColoredBox when you need just the paint, Container when several of its features combine.

open as a page

In Flutter, how does DefaultTextStyle supply styles to Text widgets, and why does text outside a Material widget show red with a yellow double underline?

level: middleimportance: should knowfreq 38%

basics

~20 s

DefaultTextStyle is an inherited widget whose style every descendant Text merges under its own style. Material and Scaffold set it from the theme's bodyMedium; text with no such ancestor under MaterialApp gets a deliberate red, double-yellow-underlined warning style.

open as a page

In Flutter, how do you render a price line mixing a struck-through old price with a bold new price, and why prefer Text.rich over RichText?

level: middleimportance: should knowfreq 45%

basics

~10 s

Build a TextSpan tree, each child with its own TextStyle, and pass it to Text.rich. Text.rich merges the ambient DefaultTextStyle, text scaling and selection support, while RichText uses only what you pass it.

open as a page

In Flutter, how would you build a coupon card with a gradient, rounded corners, an edge-to-edge image and a dashed border, and where do BoxDecoration's limits force other tools?

level: seniorimportance: should knowfreq 32%

basics

~20 s

BoxDecoration supplies the gradient, radius and shadow, but it never clips the child and cannot dash a border. Clip the image with ClipRRect or clipBehavior (Card defaults to Clip.none), and draw the dashed outline with a custom painter or a package.

open as a page