image

06 Feb 2026

9K

35K

Flutter Layout Tips: Effectively Using Spacer and Expanded

Flutter's declarative UI system makes building complex layouts intuitive. At the heart of responsive design are widgets like Row and Column, which arrange children linearly. To effectively manage the distribution of available space within these linear layouts, Flutter provides two powerful widgets: Spacer and Expanded. Mastering these two is crucial for creating flexible, adaptive UIs that look great on any screen size.

Understanding Flex Widgets

Before diving into Spacer and Expanded, it's essential to grasp the concept of flex widgets. Row and Column are subclasses of Flex. They arrange their children along a main axis (horizontal for Row, vertical for Column) and allow those children to take up available space. By default, children of a Row or Column will only take up as much space as they need. Expanded and Spacer change this behavior.

The Power of Expanded

The Expanded widget is used to make a child of a Row, Column, or Flex fill the available space along the main axis. When an Expanded widget is a child, it tells its parent to give it as much room as possible.

The Expanded widget has a flex property, which is an integer. If multiple Expanded widgets are present, they will distribute the available space among themselves proportionally according to their flex values. A widget with flex: 2 will take twice as much space as a widget with flex: 1.

Example:
Let's create a Row with three containers, where two of them are expanded.


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(
        appBar: AppBar(title: const Text('Expanded Example')),
        body: Center(
          child: Row(
            children: [
              Container(
                color: Colors.red,
                width: 50,
                height: 50,
              ),
              Expanded(
                flex: 2,
                child: Container(
                  color: Colors.green,
                  height: 50,
                  child: const Center(child: Text('Flex 2')),
                ),
              ),
              Expanded(
                flex: 1,
                child: Container(
                  color: Colors.blue,
                  height: 50,
                  child: const Center(child: Text('Flex 1')),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this example, the red Container takes its fixed width of 50. The remaining space is then divided between the green and blue Expanded containers. The green container gets two-thirds of the remaining space (due to flex: 2), and the blue container gets one-third (flex: 1).

The Role of Spacer

While Expanded is designed to make a child widget fill space, Spacer is used to create empty, flexible space between other widgets. It's essentially an Expanded widget that renders nothing. It's incredibly useful for pushing widgets apart or centering them without explicit padding.

Like Expanded, Spacer also has a flex property. You can use it to control how much empty space the Spacer takes relative to other Expanded or Spacer widgets within the same Row or Column.

Example:
Let's use Spacer to push two buttons to the ends of a Row.


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(
        appBar: AppBar(title: const Text('Spacer Example')),
        body: Center(
          child: Row(
            children: [
              ElevatedButton(
                onPressed: () {},
                child: const Text('Button 1'),
              ),
              const Spacer(), // Pushes Button 1 to the left, Button 2 to the right
              ElevatedButton(
                onPressed: () {},
                child: const Text('Button 2'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Here, Spacer() takes all available space between Button 1 and Button 2, effectively pushing them to opposite ends of the Row. If you add another Spacer with a flex value, you can create more complex spacing patterns.


// Example with multiple Spacers
Row(
  children: [
    const Spacer(flex: 1),
    ElevatedButton(onPressed: () {}, child: const Text('Center')),
    const Spacer(flex: 1),
  ],
)
// This effectively centers the button by giving equal flexible space on both sides.

Combining Spacer and Expanded

The true power comes from combining Spacer and Expanded. You can create highly dynamic and responsive layouts.

Consider a scenario where you have an icon, a title, and some text, and you want the title and text to take up remaining space, but also have some flexible padding around the icon.


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(
        appBar: AppBar(title: const Text('Combined Example')),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            children: [
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    children: [
                      const Icon(Icons.star, size: 40, color: Colors.amber),
                      const Spacer(flex: 1), // Flexible space after the icon
                      Expanded( // Title and subtitle take up remaining space
                        flex: 5,
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: const [
                            Text(
                              'Important Title',
                              style: TextStyle(
                                  fontSize: 18, fontWeight: FontWeight.bold),
                            ),
                            Text(
                              'A descriptive subtitle for the content.',
                              style: TextStyle(fontSize: 14, color: Colors.grey),
                              overflow: TextOverflow.ellipsis,
                            ),
                          ],
                        ),
                      ),
                      const Spacer(flex: 1), // Flexible space before the end
                      const Icon(Icons.arrow_forward_ios, color: Colors.grey),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this example:

  • The Icon(Icons.star) takes its intrinsic size.
  • Spacer(flex: 1) creates flexible space.
  • Expanded(flex: 5, child: Column(...)) ensures the title and subtitle take up a larger proportion of the available space, and the Column itself handles the vertical layout of text. The overflow: TextOverflow.ellipsis on the subtitle ensures it doesn't wrap unnecessarily.
  • Another Spacer(flex: 1) creates symmetrical flexible spacing.
  • The Icon(Icons.arrow_forward_ios) takes its intrinsic size.

This layout is highly responsive. If the screen size changes, the Spacer and Expanded widgets will adjust their sizes proportionally, maintaining the visual balance.

Best Practices and Tips

  1. When to use Expanded: Use Expanded when a specific child widget needs to fill the remaining space in a Row or Column, or when you want multiple children to share the space proportionally.
  2. When to use Spacer: Use Spacer when you need to create empty, flexible gaps between widgets, especially to push widgets to the edges or center them without relying on fixed padding.
  3. Avoid Redundant Nesting: Sometimes, developers might nest Rows or Columns unnecessarily. Understand the main and cross-axis alignment properties of Row and Column first (mainAxisAlignment, crossAxisAlignment) before resorting to Expanded or Spacer for simple alignment tasks. For instance, mainAxisAlignment: MainAxisAlignment.spaceBetween can often replace a Spacer between two widgets.
  4. flex Property: Leverage the flex property to control the distribution ratio precisely. Default flex is 1.
  5. Flexible vs. Expanded: Expanded is essentially a Flexible widget with fit: FlexFit.tight. Flexible offers FlexFit.loose which allows its child to be smaller than the available space, but it will not shrink smaller than its child's intrinsic size. For most "fill space" scenarios, Expanded is what you want.

Conclusion

Spacer and Expanded are indispensable tools in a Flutter developer's arsenal for creating flexible and responsive layouts. By understanding their individual roles and how they interact within Row and Column widgets, you can design UIs that gracefully adapt to various screen sizes and orientations, delivering a consistent and polished user experience. Master these widgets, and your Flutter layouts will become significantly more robust and elegant.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is