skip to content

In Flutter, how does FittedBox lay out and scale its child, and how do BoxFit.contain, cover and scaleDown differ?

level: middleimportance: should knowfreq 38%

answer

  1. child laid out unconstrained
  2. scaling happens at paint
  3. default fit is contain
  4. cover overflows unless clipped
  5. scaleDown never enlarges

basics

~20 s

FittedBox lays its child out with unbounded constraints, then scales and aligns it at paint time to fit its own size. contain (the default) fits the whole child, cover fills the box and may overflow, and scaleDown shrinks like contain but never enlarges.

solid answer

~50 s

`FittedBox` first lays its child out with **unbounded** constraints, `const BoxConstraints()`, so the child takes its natural size. It then picks its own size — as large as its constraints allow while keeping the child's aspect ratio, or smaller for `scaleDown` — and applies a **transform when painting** to scale and align the child into that box. Defaults: `fit: BoxFit.contain`, `alignment: Alignment.center`, `clipBehavior: Clip.none`. `contain` scales so the whole child is visible. `cover` scales so the child fills the box, which can overflow — set `clipBehavior: Clip.hardEdge` to crop it. `fill` distorts; `fitWidth` and `fitHeight` match one axis; `none` does not scale. `scaleDown` behaves like `contain` when the child is too big and like `none` otherwise — the usual choice for shrinking a long lecture title to one line. Because the child is unconstrained, children that want to expand, such as a `Row` with `Expanded`, fail inside it.

code

dart · 20 lines
dart
import 'package:flutter/material.dart';

class LectureTitleBadge extends StatelessWidget {
  const LectureTitleBadge({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 160,
      height: 28,
      child: FittedBox(
        fit: BoxFit.scaleDown,
        alignment: Alignment.centerLeft,
        child: Text(title, style: Theme.of(context).textTheme.titleMedium),
      ),
    );
  }
}

go deeper

for a junior

Know that FittedBox shrinks or grows a child to fit, with contain as the default and scaleDown for text that must not grow.

for a middle

Explain the unconstrained child layout, the paint-time transform, each BoxFit value and why cover needs a clip.

for a senior

Predict failures from expanding children and discuss when scaled text harms readability compared with ellipsis or wrapping.

for a principal

Set guidance that FittedBox is for short labels and fixed designs, not for making body text fit, to protect accessibility across the app.

## Two steps: lay out, then scale `FittedBox` separates **layout** from **scaling**: 1. **Lay out the child unconstrained.** The render object calls `child.layout(const BoxConstraints())`: no min, no max. A `Text` takes its single-line natural width; an `Icon` takes its size. 2. **Pick its own size.** For most fits it uses `constraints.constrainSizeAndAttemptToPreserveAspectRatio(child.size)` — as large as allowed while keeping the child's shape. For `scaleDown` it first loosens the constraints, so the box can stay at the child's natural size when that fits. 3. **Scale at paint time.** It computes a `Matrix4` from the chosen `BoxFit` and `alignment`, and paints the child through that transform. Hit testing goes through the same transform, so taps still land on the right spot. The child is never re-laid out at the smaller size. Text inside a `FittedBox` does not wrap; it stays one line and is drawn smaller. ## Defaults | Parameter | Default | |---|---| | `fit` | `BoxFit.contain` | | `alignment` | `Alignment.center` | | `clipBehavior` | `Clip.none` | ## The BoxFit values | Value | Effect | |---|---| | `contain` | as large as possible while the whole child stays inside | | `cover` | as small as possible while the child still covers the whole box; excess overflows | | `fill` | stretches each axis independently, distorting the ratio | | `fitWidth` | the child's full width is shown; height may overflow | | `fitHeight` | the child's full height is shown; width may overflow | | `none` | no scaling; aligned and anything outside is cut only if clipped | | `scaleDown` | like `contain` if the child is too big, like `none` otherwise | For `cover`, `fitWidth`, `fitHeight` and `none`, content can extend past the box. With the default `Clip.none` it paints outside; set `clipBehavior: Clip.hardEdge` to crop. ## Where it shines - **Shrink-to-fit labels.** A lecture title or a duration badge in a fixed-size slot: `FittedBox(fit: BoxFit.scaleDown, child: Text(...))` shrinks long text and leaves short text alone. - **Scaling a fixed design.** A widget built at one size — a diagram or a scoreboard — scaled uniformly to whatever space is available. - **Icons and logos** that should fill a slot without distortion. ## Where it bites - **Expanding children.** A child laid out with infinite constraints cannot fill space. A `Row` with an `Expanded` child, a `ListView`, or an `AspectRatio` with nothing bounded will fail or misbehave. Give the child an explicit `SizedBox` size and let `FittedBox` scale that. - **Accessibility.** Shrinking text to fit can make it unreadably small at large text settings; prefer wrapping or ellipsis for body text, and reserve `FittedBox` for short labels. - **Mistaking it for image fitting.** `Image` has its own `fit` parameter that uses the same `BoxFit` values; wrapping an image in a `FittedBox` is rarely needed. ## Summary Lay out unconstrained, size to the constraints while preserving shape, scale at paint time. Know the default, `contain`, and that `cover` needs a clip to crop.

  • Why does a Row containing an Expanded child fail inside a FittedBox?
    `FittedBox` lays its child out with unbounded constraints, so the `Row` receives infinite width. `Expanded` needs a finite width to divide, and `RenderFlex` reports an error. Give the `Row` a fixed width with a `SizedBox` and let `FittedBox` scale the result.
  • Why does BoxFit.cover inside a FittedBox show content outside its bounds?
    `cover` scales the child until it fills the box, so one axis is larger than the box. `FittedBox` defaults to `clipBehavior: Clip.none`, so the excess is painted. Set `clipBehavior: Clip.hardEdge` to crop it.

saying these in an interview costs you the question

  • FittedBox passes its own constraints down to the child.
  • FittedBox's default fit is BoxFit.scaleDown.
  • BoxFit.cover crops the child automatically.
  • Text inside a FittedBox wraps onto more lines to fit.
  • BoxFit.scaleDown can enlarge a small child.