The moment you build a row of tags or filter chips, a plain Row betrays you — the children spill off the edge and Flutter paints the yellow-and-black overflow stripes. Wrap is the fix: it lays children out like a Row, but when the next child won't fit, it flows onto a new line instead of overflowing. This guide covers how Wrap works, the spacing and runSpacing gaps, alignment across and within runs, a real chip layout, and the one constraint that trips people up — with paste-ready code.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowEvery snippet below is paste-ready against current stable Flutter. Wrap sits right next to Row and Column in your toolkit — reach for it whenever the count or width of children is dynamic.
We'll start with a basic Wrap, add the two kinds of spacing, control alignment across and within the runs, build a tag layout, and finish with when Wrap fails to wrap.
If you'd rather watch chips and tag inputs built on screen, the channel walks through real, responsive Flutter UIs.
The basic Wrap
Wrap takes a list of children and lays them along the main axis — horizontal by default. When the next child would exceed the available width, it starts a new run (a new line) rather than overflowing.
Wrap(
children: [
Chip(label: Text('Dart')),
Chip(label: Text('Flutter')),
Chip(label: Text('Firebase')),
Chip(label: Text('Riverpod')),
Chip(label: Text('Animations')),
],
)
Drop the same children into a Row on a narrow screen and you'd get an overflow error. Wrap simply flows them onto as many lines as it needs — which is exactly what you want for content whose size you can't predict.
spacing and runSpacing
By default the children are flush against each other, which looks cramped. Wrap gives you two independent gaps: spacing is the gap between children in the same run, and runSpacing is the gap between the runs.
Wrap(
spacing: 8, // horizontal gap between chips in a row
runSpacing: 12, // vertical gap between the wrapped lines
children: tags.map((t) => Chip(label: Text(t))).toList(),
)
Forgetting runSpacing is the most common slip — the chips look fine on one line, then collapse together vertically the moment they wrap. Set both whenever you build a multi-line chip or tag layout.
Alignment across and within runs
Wrap has three alignment knobs. alignment positions children along the main axis within each run (using WrapAlignment), runAlignment positions the runs themselves along the cross axis, and crossAxisAlignment (a WrapCrossAlignment) aligns children of differing heights within a single run.
Wrap(
alignment: WrapAlignment.center,
runAlignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 8,
runSpacing: 8,
children: chips,
)
WrapAlignment offers the same family you know from Row and Column — start, end, center, spaceBetween, spaceAround, and spaceEvenly — but applied per run, which is what makes centred, evenly distributed chip clouds easy.

Build real, responsive Flutter layouts
The Complete Flutter Guide takes you from layout fundamentals to shipped, adaptive apps step by step.
Enrol nowA real tag layout
Tags built from a list of strings are the canonical use case. Map each string to a styled container and let Wrap flow them onto as many lines as the screen allows.
Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final tag in interests)
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.deepPurple.shade50,
borderRadius: BorderRadius.circular(20),
),
child: Text(tag),
),
],
)
Because the layout is data-driven, you never have to worry about how many tags arrive or how long each one is — the same widget handles three short tags or thirty long ones. For interactive filters, swap the Container for a FilterChip or ChoiceChip from the Material catalogue.
Going vertical
Set direction: Axis.vertical and Wrap stacks children top to bottom, starting a new column when one fills the available height. The meaning of the two gaps swaps: spacing becomes the vertical gap within a column and runSpacing the horizontal gap between columns. The vertical form needs a bounded height to know when to wrap.
When Wrap fails to wrap
Wrap can only wrap if it knows where the edge is — it needs a bounded width (or height, when vertical). Place it inside something that hands it unbounded width — a horizontal ListView, or a Row without an Expanded around it — and it lays everything on one infinitely wide run, then overflows. Give it a bounded width with Expanded, a SizedBox, or simply by letting it take the full screen width, and it wraps as expected.
Wrap vs Row
Use a Row when you have a small, fixed set of children that you know will fit — a leading icon, a title, a trailing button. Reach for Wrap the moment the children are dynamic in count or width: tags, chips, attachment thumbnails, selectable options. The rule of thumb: if a designer could add "one more" of something and break your Row, it should have been a Wrap.
Common mistakes
- Using Row for tags. Dynamic content overflows;
Wrapflows onto new lines. - Setting spacing but not runSpacing. Wrapped lines collapse together vertically.
- Wrap inside unbounded width. A horizontal
ListViewgives infinite width, so it never wraps. - Confusing alignment and runAlignment. One aligns within a run, the other aligns the runs.
- Reaching for Wrap when a Row fits. For a fixed, known set,
Rowis simpler and clearer.
Frequently asked questions
What is the difference between Row and Wrap in Flutter?
A Row keeps everything on one line and overflows if it doesn't fit; Wrap moves the next child to a new run instead. Use Wrap for dynamic content like tags and chips.
What is the difference between spacing and runSpacing in Flutter Wrap?
spacing is the gap between children in the same run; runSpacing is the gap between the runs. Set both for multi-line chip layouts.
Can Wrap go vertical in Flutter?
Yes — set direction: Axis.vertical. Children stack into columns, and the two gaps swap meaning. The vertical form needs a bounded height.
Why does Wrap overflow or fail to wrap in Flutter?
It needs a bounded width to know when to start a new run. Inside unbounded width — a horizontal ListView, for instance — it never wraps. Constrain its width and it works.
Further reads
Keep going with the tutorials that pair with this guide:
- Flutter Development Guide 2026 — the full Flutter hub.
- Flutter Row and Column — the single-line cousins of Wrap.
- Flutter Expanded vs Flexible vs Spacer — give a Wrap a bounded width inside a Row.
- Flutter Material Widgets Catalogue — Chip, FilterChip, and ChoiceChip for interactive tags.
- Flutter Layout Widgets Guide — the constraints rule behind every layout widget.
Sources: Flutter documentation — Wrap, WrapAlignment, WrapCrossAlignment, Axis, and Chip API references, plus the layout and constraints guides (docs.flutter.dev). Verified against current stable Flutter.