skip to content

In Flutter, how do a Row's spacing parameter, Spacer and a SizedBox gap differ, and when should a Wrap replace the Row?

level: middleimportance: should knowfreq 38%

answer

  1. fixed gap versus flexible gap
  2. spacing arrived in 3.27
  3. only between children
  4. Spacer is Expanded with an empty child
  5. Wrap: runs instead of overflow

basics

~20 s

Row.spacing puts a fixed gap between adjacent children; a SizedBox is a fixed gap you place yourself; Spacer is an Expanded that absorbs leftover space. Use Wrap when children should flow onto new lines instead of overflowing.

solid answer

~40 s

Since Flutter 3.27, `Row` and `Column` take `spacing`: a fixed main-axis gap between adjacent children, never before the first or after the last, that adds to any gaps `mainAxisAlignment` creates and counts toward the children's total size, so it can contribute to overflow. A `SizedBox(width: 8)` does the same for one position only, useful when gaps differ. `Spacer` is different in kind: it is `Expanded(flex: flex, child: SizedBox.shrink())`, a flexible child that takes a share of the free space and pushes neighbours apart, so it leaves `mainAxisAlignment` nothing to distribute. When the children's total width can exceed the available width and they should flow onto more lines — chips, tags, allergen badges — use `Wrap` with `spacing` and `runSpacing`. `Wrap` has no flex, so `Expanded` inside it is an error.

code

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

Widget labelHeader() => const Row(
      children: [
        Text('Amount per serving'),
        Spacer(), // flexible: takes all leftover width
        Text('% Daily Value'),
      ],
    );

Widget allergenBadges(List<String> allergens) => Wrap(
      spacing: 8, // between badges in a run
      runSpacing: 4, // between runs
      children: [for (final String a in allergens) Chip(label: Text(a))],
    );

go deeper

for a junior

Recall the three gap tools, that Spacer takes leftover space, and that Wrap moves children to a new line instead of overflowing.

for a middle

Explain how spacing combines with mainAxisAlignment and counts toward size, and why Spacer is an Expanded under the hood.

for a senior

Choose between Row and Wrap from the content's variability, and catch Spacer or Expanded misuse in unbounded or non-flex parents during review.

for a principal

Standardise gap handling (spacing versus spacing-scale tokens and wrappers) across a component library so layouts stay consistent and migrations to new APIs are cheap.

## Three ways to make a gap Putting space between children of a `Row` or `Column` is so common that Flutter now has three tools with different behaviour. | Tool | Gap size | Where | Takes flex space? | Available since | |---|---|---|---|---| | `spacing:` on `Row`/`Column`/`Flex` | fixed | between every adjacent pair | no | 3.27 | | `SizedBox(width: …)` / `SizedBox(height: …)` | fixed | exactly where you put it | no | always | | `Spacer(flex: n)` | whatever is left | where you put it | yes | always | ## The spacing parameter `spacing` (default `0.0`, must not be negative) was added to `Flex`, and therefore `Row` and `Column`, in Flutter 3.27. Its rules, from the framework documentation: - It is applied **only between children** on the main axis, never before the first child or after the last. - It **combines with `mainAxisAlignment`**. With `spaceBetween` and 100 pixels of free space between two children and `spacing: 10`, there are at least 10 pixels between them plus the distributed free space. - It is **part of the size**. The flex algorithm starts its running total at `spacing * (childCount - 1)`, so a `Row` with ten children and `spacing: 12` needs 108 pixels of gaps. With `MainAxisSize.min` the Row is that much larger; in a tight space it can overflow. Before 3.27 the idiom was a `SizedBox` between each pair, often generated with a helper. `spacing` replaces that for uniform gaps; the framework itself migrated many `SizedBox` gaps to it in later releases. ## SizedBox gaps A `SizedBox(width: 8)` in a `Row` is simply an inflexible child with a fixed width and no content. Use it when: 1. Gaps differ between positions, for example a wider gap before the amount on a nutrition row. 2. You need one gap next to a conditional child without adding spacing everywhere. ## Spacer: a flexible gap `Spacer` builds `Expanded(flex: flex, child: const SizedBox.shrink())`, with `flex` defaulting to 1 and required to be positive. Consequences: - It consumes free space, so `mainAxisAlignment` has nothing left to distribute. - Several spacers split the free space by their flex factors: `Spacer(flex: 2)` gets twice the space of `Spacer()`. - It competes with `Expanded` and `Flexible` siblings for the same leftover space. - It is a flex child, so it fails in a Row whose width is unbounded, for example inside a horizontal scroll view. On a nutrition-label header, `Row(children: [Text('Amount per serving'), Spacer(), Text('% Daily Value')])` pins the two labels to opposite edges, the same effect as `spaceBetween` with two children. ## When Wrap replaces Row A `Row` lays everything on one line. If the children do not fit, it overflows. `Wrap` lays children along its `direction` (default `Axis.horizontal`) and starts a new **run** when the next child does not fit. Its main parameters: - `spacing` — gap between children within a run (default `0.0`). - `runSpacing` — gap between runs (default `0.0`). - `alignment` — how children are placed within a run (`WrapAlignment.start` by default; also `end`, `center`, `spaceBetween`, `spaceAround`, `spaceEvenly`). - `runAlignment` — how the runs are placed in the cross axis. - `crossAxisAlignment` — how children of different heights align within a run (`WrapCrossAlignment.start` by default). Use `Wrap` for chips, filter tags or allergen badges under a nutrition label: the count and label lengths vary, and flowing onto another line is better than truncating. Keep `Row` when the design is one line with a flexible child that should shrink, such as a name that ellipsises. `Wrap` does not support flex: its children are laid out at their natural size. An `Expanded` or `Flexible` directly inside it reports 'Incorrect use of ParentDataWidget.', because those widgets only work under a `Flex`.

  • In Flutter, does Row(spacing: 16) with MainAxisAlignment.spaceBetween put 16 pixels at the ends?
    No. `spacing` is applied only between adjacent children, never before the first or after the last. With `spaceBetween`, the ends stay flush and each gap is the distributed free space plus 16. With `spaceAround` or `spaceEvenly`, the end gaps come from the free space alone.
  • Why can adding spacing to a Flutter Row cause an overflow that was not there before?
    The flex algorithm counts `spacing * (childCount - 1)` as used main-axis space before sizing flexible children. A row that barely fit now needs that much more width, and if the inflexible children plus gaps exceed the Row's maximum, it overflows just as if you had added fixed `SizedBox` gaps.

saying these in an interview costs you the question

  • Row spacing also adds space before the first and after the last child.
  • Spacer and SizedBox(width: 8) behave the same way.
  • Spacing is free and cannot contribute to an overflow.
  • Expanded inside a Wrap fills the rest of the current run.
  • mainAxisAlignment still distributes space when a Spacer is present.