The yellow-and-black overflow stripe is one of the most common things Flutter developers run into. You build a list, put a Row inside each item, and suddenly the debug console fills with:
A RenderFlex overflowed by 42 pixels on the right.
It happens most often when a Row sits inside a ListView or ListView.builder, and one or more children — usually a Text widget or an image — is wider than the available space. This guide walks through exactly why it happens and gives you every fix with working code.
What the full error message looks like
In debug mode, Flutter prints a message like this to the console:
══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞══════════════════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 83 pixels on the right.
The relevant error-causing widget was:
Row
Row:file:///path/to/your/widget.dart:24:14
The overflowing RenderFlex has an orientation of Axis.horizontal.
The edge of the RenderFlex that is overflowing has been marked in the
rendering with a yellow and black striped pattern. This is usually
caused by the contents being too big for the RenderFlex.
Consider applying a flex factor (e.g. using an Expanded widget) to
force the children of the RenderFlex to fit within the available
space instead of being sized to their natural size.
This is considered an error condition because it indicates that there
is content that cannot be seen. If the content is legitimately bigger
than the available space, consider clipping it with a ClipRect widget
before putting it in the flex, or using a scrollable container rather
than a Flex, like a SingleChildScrollView.
Flutter is telling you exactly what happened and even suggesting a fix. This guide explains each option in detail so you can pick the right one for your situation.
Why Flutter layouts work the way they do
Before fixing anything, it helps to understand the fundamental rule Flutter uses to lay out widgets. Flutter layout follows two steps:
- Constraints go down. A parent widget passes size constraints (minimum width, maximum width, minimum height, maximum height) to each child.
- Sizes go up. Each child reports back the actual size it wants to be, within those constraints.
A Row is a Flex widget with Axis.horizontal as its main axis. When a Row lays out its children, it first gives every non-flex child (anything not wrapped in Expanded or Flexible) unlimited horizontal space. Each child reports back the width it naturally wants. If the sum of those natural widths exceeds the Row's own width, you get the overflow error.
Why ListView makes this worse
A ListView is a scrollable widget. It scrolls vertically by default, which means it gives its children unbounded height — children can be as tall as they like. In the horizontal direction, however, ListView passes its own width down to each child as both the minimum and maximum constraint. That means each child of a ListView is forced to be exactly as wide as the ListView itself.
This becomes a problem when you place a Row inside a ListView item. The Row receives the ListView's full width, say 390 logical pixels on a typical phone. The Row then asks its children how wide they want to be. If a Text widget contains a long string and has no constraint, it will report a natural width of perhaps 500 pixels. The Row has only 390 pixels available, so it overflows by 110 pixels.
Step 1: Reproduce the error with minimal code
Here is the smallest code example that triggers the overflow. Paste this into a new Flutter project to see the yellow-and-black stripes yourself:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
children: [
const Icon(Icons.person, size: 48),
// This Text has no constraints and will overflow
// on narrow screens or with long strings
const Text(
'This is a very long username that will definitely '
'overflow the available width on any phone screen',
),
const Text('Extra info here'),
],
);
},
),
),
);
}
}
Run this in debug mode. You will see the overflow stripes on the right side of each list item and the error in the console. Now let us fix it.
Fix 1: Wrap the overflowing child with Expanded
Expanded is the most common fix. It tells a child of a Row or Column to take up all the remaining space after the other children have been measured. This is the right choice when you want one child to fill the leftover width.
Here is how to apply it to the example above:
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
children: [
const Icon(Icons.person, size: 48),
// Wrap the Text that was overflowing with Expanded
const Expanded(
child: Text(
'This is a very long username that will definitely '
'overflow the available width on any phone screen',
// Optional: truncate with ellipsis if still too long
overflow: TextOverflow.ellipsis,
),
),
const Text('Extra info here'),
],
);
},
),
What happens step by step:
- The
Rowfirst measures its non-flex children: theIcon(48 px) and the lastText(its natural width, say 90 px). Total fixed usage: 138 px. - The remaining space is 390 − 138 = 252 px.
Expandedgives exactly those 252 px to the middleTextas both its minimum and maximum width constraint.- The
Textmust fit within 252 px. Withoverflow: TextOverflow.ellipsis, it truncates gracefully.
When to use Expanded: Use Expanded when one child should fill all the leftover space and you do not care if it gets very wide or very narrow.
Fix 2: Use Flexible instead of Expanded
Flexible is like Expanded but with an important difference: the child is allowed to be smaller than the available space. With Expanded, the child is forced to fill the remaining space exactly. With Flexible, the child takes up at most the remaining space but can choose to be smaller.
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
children: [
const Icon(Icons.person, size: 48),
// Flexible lets the Text be as small as its content,
// up to a maximum of the remaining space
const Flexible(
child: Text(
'Username',
overflow: TextOverflow.ellipsis,
),
),
const Text('Active'),
],
);
},
),
With Flexible, if the text is short (say "Ali"), the Row will not stretch that child to fill the full remaining width. The layout will look more natural for variable-length content. With Expanded, a short name would still fill the entire middle space, pushing "Active" to the far right.
When to use Flexible: Use Flexible when the child should shrink to fit its content but must not exceed the available space. This is common for labels and names in list items.
Fix 3: Constrain the child with SizedBox
Sometimes you know exactly how wide a widget should be. Wrapping it in a SizedBox with an explicit width gives it a hard constraint and prevents it from expanding beyond that.
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
children: [
const Icon(Icons.person, size: 48),
// Give this Text a fixed maximum width
const SizedBox(
width: 200,
child: Text(
'This is a very long username that will not overflow '
'because it has a fixed width constraint',
overflow: TextOverflow.ellipsis,
),
),
const Text('Active'),
],
);
},
),
Step by step:
- The
Rowmeasures theIcon: 48 px. - The
Rowmeasures theSizedBox: exactly 200 px. - The
Rowmeasures "Active": its natural width, say 50 px. - Total: 298 px, which is less than 390 px. No overflow.
When to use SizedBox: Use a fixed SizedBox width when the design spec calls for a specific column width that should not vary, regardless of the device width. This is fragile on very small or very large screens, so prefer Expanded or Flexible for general lists.
Fix 4: Use ConstrainedBox for a maximum width
ConstrainedBox lets you set a maximum width without fixing an exact value. The child can be smaller but cannot exceed the maximum.
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
children: [
const Icon(Icons.person, size: 48),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 220),
child: const Text(
'Username that could be very long',
overflow: TextOverflow.ellipsis,
),
),
const Spacer(),
const Text('Active'),
],
);
},
),
The Spacer widget here fills any remaining horizontal space between the constrained text and the "Active" label at the far right. Spacer is equivalent to Expanded(child: SizedBox.shrink()).
When to use ConstrainedBox: Use it when a widget has a known upper limit but should shrink gracefully for short content. Works well for profile names, product titles, or any label where content length varies unpredictably.
Fix 5: Replace Row with Wrap for line-breaking content
Sometimes the overflow happens because you have multiple tags, chips, or badges in a row that can wrap naturally onto a second line. In this case, replacing Row with Wrap solves the problem without any flex widgets.
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
// Wrap automatically moves children to the next line
// when they do not fit horizontally
child: Wrap(
spacing: 8, // horizontal gap between children
runSpacing: 4, // vertical gap between lines
children: [
Chip(label: Text('Flutter')),
Chip(label: Text('Dart')),
Chip(label: Text('Mobile Development')),
Chip(label: Text('Cross Platform')),
Chip(label: Text('Open Source')),
],
),
);
},
),
Wrap works exactly like Row when everything fits. When a child does not fit on the current line, Wrap moves it to the next line. There is no overflow because Wrap expands vertically to accommodate extra lines.
When to use Wrap: Use Wrap for tag lists, chip groups, badge rows, or any collection of items where wrapping to a new line makes visual sense. Do not use it for structured rows where alignment across items matters, such as table-like layouts.
Fix 6: Use FractionallySizedBox for proportional widths
FractionallySizedBox sizes its child as a fraction of the available space. This is useful when you want the first column to always take up a specific percentage of the row width regardless of screen size.
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
children: [
// Label always takes 40% of the row width
FractionallySizedBox(
widthFactor: 0.4,
child: Text(
'Label that might be long',
overflow: TextOverflow.ellipsis,
),
),
// Value takes the remaining 60%
Expanded(
child: Text(
'Value content here',
overflow: TextOverflow.ellipsis,
),
),
],
);
},
),
Note that FractionallySizedBox inside a Row needs the Row's width to be bounded. Since the ListView provides a bounded width to the Row, this works correctly. The fraction is calculated from the tight constraint the ListView passes down.
When to use FractionallySizedBox: Use it for two-column layouts where a consistent ratio is required across all screen sizes, such as a key–value list or a settings screen.
Fix 7: Add mainAxisSize: MainAxisSize.min to the Row
This fix is less common but useful in a specific scenario: when the Row itself does not need to fill the full available width. By default, a Row in a ListView item is forced to fill the full width because ListView gives it tight horizontal constraints. But if you have wrapped the Row inside an IntrinsicWidth or a widget that provides loose constraints, adding mainAxisSize: MainAxisSize.min tells the Row to shrink to its children's total width.
// This pattern works when the Row is inside a widget
// that gives it loose (not tight) horizontal constraints
IntrinsicWidth(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star),
const Text('Rating'),
],
),
),
By itself inside a ListView, mainAxisSize: MainAxisSize.min does not prevent overflow because ListView still gives the children tight width constraints. It becomes useful when you want a horizontally-scrollable row (wrapping the Row in a SingleChildScrollView with scrollDirection: Axis.horizontal).
Handling a nested Row inside Column inside ListView
A common pattern is a ListTile-style layout where a Column holds the text content, and that Column sits inside a Row alongside an avatar or icon. This nested structure can overflow if the Column is not constrained.
Here is the broken version:
// This overflows because the Column's children are unbounded
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const CircleAvatar(radius: 24),
const SizedBox(width: 12),
// Column is unbounded in width — it will overflow
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('Very long display name that overflows'),
const Text('Secondary line with more text that also overflows'),
],
),
],
);
},
),
Fix it by wrapping the Column in Expanded:
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const CircleAvatar(radius: 24),
const SizedBox(width: 12),
// Expanded gives the Column all the remaining horizontal space
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Very long display name that no longer overflows',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
const Text(
'Secondary line with more text that is also constrained',
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
],
),
),
],
),
);
},
),
Step by step:
- The
ListViewpasses a tight horizontal constraint (full screen width, say 390 px) to thePadding. Paddingsubtracts 32 px (16 each side) and passes 358 px to theRow.- The
Rowmeasures theCircleAvatar: 48 px. Measures theSizedBox: 12 px. Fixed total: 60 px. Expandedgives theColumnexactly 358 − 60 = 298 px.- The
Columngives eachTexta maximum of 298 px. TheTextwidgets wrap or truncate within that space.
Fixing Text overflow specifically
Even after applying Expanded, a Text widget may still feel cramped if it does not know how to handle content that is still too long. The Text widget has two relevant properties:
overflow: what to do when the text does not fit. Options areTextOverflow.ellipsis(shows "..."),TextOverflow.clip(cuts off hard),TextOverflow.fade(fades out), orTextOverflow.visible(renders outside the box, but the parent must clip it).maxLines: limits how many lines the text can occupy. Without this, long text wraps to as many lines as needed.softWrap: if set tofalse, the text never wraps and overflow behaviour applies to the entire string.
A robust list item text usually combines all three:
Expanded(
child: Text(
item.title,
overflow: TextOverflow.ellipsis,
maxLines: 2,
softWrap: true,
),
),
This tells Flutter: the text can wrap to two lines, and if it still does not fit after two lines, truncate with an ellipsis.
Fixing image overflow inside Row inside ListView
Images inside rows commonly overflow because Image.network or Image.asset without a size constraint will request its natural dimensions. A 1200 × 800 image will try to render at 1200 px wide, immediately overflowing any phone screen.
Here is the broken pattern:
Row(
children: [
Image.network('https://example.com/photo.jpg'),
const Text('Photo caption'),
],
),
Fix it by constraining the image with SizedBox or by wrapping it in Expanded:
// Option A: fixed-size thumbnail
Row(
children: [
SizedBox(
width: 80,
height: 80,
child: Image.network(
'https://example.com/photo.jpg',
fit: BoxFit.cover,
),
),
const SizedBox(width: 12),
const Expanded(
child: Text(
'Photo caption that can be as long as needed',
overflow: TextOverflow.ellipsis,
maxLines: 2,
),
),
],
),
// Option B: image fills remaining space after text
Row(
children: [
const SizedBox(
width: 160,
child: Text(
'Caption',
overflow: TextOverflow.ellipsis,
),
),
Expanded(
child: AspectRatio(
aspectRatio: 16 / 9,
child: Image.network(
'https://example.com/photo.jpg',
fit: BoxFit.cover,
),
),
),
],
),
The key rule: every Image inside a Row must either have an explicit width or be wrapped in Expanded or Flexible. Without one of these, the image will render at its natural size and overflow.
Fixing overflow in horizontally scrollable rows
Sometimes the correct fix is not to constrain the content but to make the row scrollable. If you have a row of items that should all be fully visible (no truncation), wrap the Row in a SingleChildScrollView with horizontal scroll direction:
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: List.generate(
8,
(i) => Padding(
padding: const EdgeInsets.all(8),
child: Chip(label: Text('Category ${i + 1}')),
),
),
),
);
},
),
This is appropriate for carousels, tag rows, or any situation where showing all items without truncation is more important than fitting within the screen width. Avoid this pattern for the main content of a list item — use Expanded or truncation there.
Common mistakes that cause or worsen overflow
- Forgetting Expanded when nesting Column inside Row. The Column's children are unbounded unless the Column itself is constrained. Always wrap the Column in Expanded.
- Using two Expanded children with flex: 1 each and expecting equal halves. This works correctly — each gets half — but if the Row itself is inside another unbounded context, you get a different error. Ensure the Row has bounded width first.
- Applying Expanded outside a Row or Column. Expanded only works as a direct child of a Row, Column, or Flex. Using it elsewhere gives a "Expanded widgets must be placed inside Flex widgets" error.
- Setting width: double.infinity on a child inside a Row. This forces the child to be infinitely wide, which immediately overflows. Use Expanded instead.
- Using mainAxisSize: MainAxisSize.min inside ListView without wrapping in IntrinsicWidth. As noted above, ListView's tight width constraints mean the Row fills the available width regardless of mainAxisSize unless you explicitly loosen the constraints.
- Assuming ListView.builder items have intrinsic height. ListView items have unbounded height and bounded width. Layout issues are always about the horizontal axis, not the vertical one.
Decision tree: which fix to use
Use this quick reference to choose the right approach:
- One child should fill all leftover space → Use
Expandedon that child. - One child should take up to its natural width but not more than the leftover space → Use
Flexibleon that child. - The design requires a specific pixel width for a column → Use
SizedBox(width: N). - The design requires a percentage-based column width → Use
FractionallySizedBox(widthFactor: 0.X). - Items in the row are chips or tags that should wrap to a new line → Replace
RowwithWrap. - All items must be fully visible with no truncation → Wrap
RowinSingleChildScrollView(scrollDirection: Axis.horizontal). - A nested Column inside a Row is overflowing → Wrap the
ColumninExpanded. - An image is overflowing → Wrap in
SizedBoxwith explicit dimensions or inExpanded.
A complete realistic list item
Here is a full example combining several of these techniques into a production-style list item. This item shows an avatar, a name with a badge, a subtitle, and a trailing action button.
import 'package:flutter/material.dart';
class ContactListItem extends StatelessWidget {
final String name;
final String subtitle;
final bool isOnline;
final VoidCallback onTap;
const ContactListItem({
super.key,
required this.name,
required this.subtitle,
required this.isOnline,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 1. Avatar — fixed size, never participates in flex
Stack(
children: [
const CircleAvatar(
radius: 24,
backgroundImage: NetworkImage(
'https://i.pravatar.cc/48',
),
),
if (isOnline)
Positioned(
right: 0,
bottom: 0,
child: Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
border: Border.all(color: Colors.white, width: 2),
),
),
),
],
),
const SizedBox(width: 12),
// 2. Name + subtitle column — Expanded to take all remaining space
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
// Name: Flexible so it shrinks for short names
Flexible(
child: Text(
name,
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 15,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
if (isOnline) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.green.shade100,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'Online',
style: TextStyle(
fontSize: 11,
color: Colors.green,
fontWeight: FontWeight.w500,
),
),
),
],
],
),
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade600,
),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
),
// 3. Trailing action — fixed size, right-aligned
const SizedBox(width: 8),
IconButton(
icon: const Icon(Icons.message_outlined),
onPressed: onTap,
),
],
),
),
);
}
}
// Usage
class ContactsPage extends StatelessWidget {
const ContactsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Contacts')),
body: ListView.separated(
itemCount: 20,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) => ContactListItem(
name: index % 3 == 0
? 'Alexander the Very Long Named Person'
: 'Alex',
subtitle: 'Last seen ${index + 1} hours ago',
isOnline: index % 2 == 0,
onTap: () {},
),
),
);
}
}
This example handles every overflow scenario:
- The avatar is fixed at 48 px diameter and never expands.
- The inner
Columnis wrapped inExpanded, so it takes all horizontal space the avatar and trailing button do not use. - Inside the name row, the name
Textis wrapped inFlexible, so a short name does not push the "Online" badge to the right unnecessarily. - Both
Textwidgets useoverflow: TextOverflow.ellipsisandmaxLines, so they truncate rather than overflow. - The trailing
IconButtonis fixed and comes after theExpandedcolumn, so it is always visible at the right edge.
How to confirm the fix worked
After applying a fix, confirm it in three ways:
- Check the debug console. If the overflow error is gone, the console will stop reporting it. Run the app and scroll through the entire list to ensure no item triggers the error on any content length.
- Enable the performance overlay. In
MaterialApp, setshowPerformanceOverlay: true. Overflow causes extra paint work. A clean layout will show green bars. - Use Flutter DevTools Inspector. Open DevTools, go to the Widget Inspector tab, and enable "Show guidelines". This draws the layout constraints visually. Look for any widget whose painted area extends outside its constraint box — that indicates a remaining overflow, even if it is clipped and not immediately visible.
- Test with the longest realistic content. Overflow bugs often hide during development because test data is short. Write a test list item that uses the maximum expected string length for every text field and verify the layout holds.
Writing a widget test to catch future regressions
Once the layout is fixed, add a widget test to prevent regressions. Flutter's test framework will fail the test if an overflow occurs during the test run:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_app/contact_list_item.dart';
void main() {
testWidgets('ContactListItem does not overflow with long name',
(WidgetTester tester) async {
// Use a standard phone-width surface
tester.view.physicalSize = const Size(390, 844);
tester.view.devicePixelRatio = 3.0;
addTearDown(tester.view.reset);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: ContactListItem(
// Very long name to stress-test the layout
name: 'Alexander Bartholomew Christofferson-Wellington III',
subtitle: 'Last active 3 days ago — long subtitle text',
isOnline: true,
onTap: () {},
),
),
),
);
// If there is an overflow, pumpWidget will throw an assertion.
// No explicit expect needed — the absence of an exception IS the test.
expect(tester.takeException(), isNull);
});
}
Run this with flutter test. If a future code change re-introduces overflow, this test will catch it immediately.
How to apply this in a production Flutter codebase
The overflow fix is straightforward once you understand constraints. The harder part is applying the right fix consistently across a large app. In production code:
- Every
Rowthat containsTextshould have at least one child wrapped inExpandedorFlexible, unless every child has an explicit size constraint. - Images inside rows should always have an explicit width or be inside
Expanded. - Nested
ColumninsideRowshould always have theColumninsideExpanded. - Add widget tests for list items with stress-test data to prevent regressions when content changes.
Official references
These official references are useful if you need the product or framework documentation alongside this guide.
- Flutter docs: Understanding constraints
- Flutter docs: Layouts in Flutter
- Flutter API: Expanded class
- Flutter API: Flexible class
- Flutter API: Wrap class
Related guides on this site
If you want to keep going without opening dead ends, these are the most useful next reads from this site.
- Flutter Performance in 2026: Impeller, DevTools, and Rebuild Reduction
- Responsive Flutter UI for Mobile, Tablet, Desktop, and Web
- Flutter App Architecture in 2026: A Practical Feature-First Guide
- Flutter Testing Strategy in 2026: Unit, Widget, Integration, and Goldens
Sources & Further Reading
Related Posts
Need a structured Flutter learning path?
My Flutter and Dart training focuses on production habits, architecture choices, and the practical skills teams need to ship and maintain apps.
Explore Flutter courses