image

01 Mar 2026

9K

35K

Flutter Layout Tips: Using Expanded & Flexible for Responsive Grids

Creating responsive layouts is crucial for any modern application, ensuring a seamless user experience across various screen sizes and orientations. In Flutter, achieving adaptable designs, especially for grid-like structures, can be elegantly managed using the Expanded and Flexible widgets. These two powerful widgets are fundamental for distributing available space among children of a Row or Column, allowing your UI to fluidly adjust.

Understanding Expanded

The Expanded widget is used to expand a child of a Row, Column, or Flex so that the child fills the available space along the main axis. When multiple Expanded children are present, they divide the available space according to their flex factor.

Consider a scenario where you want two widgets in a row to take up equal space:


Row(
  children: [
    Expanded(
      child: Container(
        color: Colors.red,
        height: 100,
      ),
    ),
    Expanded(
      child: Container(
        color: Colors.blue,
        height: 100,
      ),
    ),
  ],
)

In this example, both containers will take up 50% of the available width. You can control the proportion using the flex property. A widget with flex: 2 will take twice as much space as a widget with flex: 1 (assuming siblings are also Expanded or Flexible with a defined flex factor).


Row(
  children: [
    Expanded(
      flex: 1, // Takes 1 part of the space
      child: Container(
        color: Colors.red,
        height: 100,
      ),
    ),
    Expanded(
      flex: 2, // Takes 2 parts of the space
      child: Container(
        color: Colors.blue,
        height: 100,
      ),
    ),
  ],
)

Here, the blue container will be twice as wide as the red container.

Understanding Flexible

The Flexible widget is similar to Expanded but offers more control over how a child fills the available space. While Expanded forces its child to fill all available space along the main axis, Flexible allows its child to be constrained by its own size while still taking up available space up to its constraints.

The key difference lies in the fit property:

  • FlexFit.tight: Behaves identically to Expanded, forcing the child to fill the available space.
  • FlexFit.loose: Allows the child to be smaller than the available space if it desires. The child will occupy space up to its intrinsic size, or the available space if its intrinsic size is larger.

Row(
  children: [
    Flexible(
      flex: 1,
      fit: FlexFit.loose, // Child can be smaller than available space
      child: Container(
        color: Colors.red,
        width: 50, // This width will be respected if space allows
        height: 100,
      ),
    ),
    Flexible(
      flex: 1,
      fit: FlexFit.tight, // Behaves like Expanded
      child: Container(
        color: Colors.blue,
        height: 100,
      ),
    ),
  ],
)

In this example, the red container will try to be 50 logical pixels wide. If there's more space available for its flexible portion, it will not expand beyond 50 pixels (due to FlexFit.loose and its explicit width). The blue container, however, will fill the remaining flexible space.

Building Responsive Grids with Expanded & Flexible

To create responsive grids, you typically combine Row and Column widgets, using Expanded or Flexible within the Rows to dictate how items distribute horizontally. For vertical responsiveness, these widgets can also be used within Columns. For dynamic grid column counts based on screen width, a common pattern involves using LayoutBuilder or MediaQuery to conditionally render different widget trees, where Expanded and Flexible then manage the space distribution within those specific layouts.

Example: A Responsive Card Layout (Two Columns on Larger Screens, One Column on Smaller)

Here's an example demonstrating how LayoutBuilder can determine the overall structure, and then Expanded ensures items within a row divide available space responsively.


import 'package:flutter/material.dart';

class ResponsiveGridPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Responsive Grid with Expanded & Flexible')),
      body: LayoutBuilder(
        builder: (context, constraints) {
          // Determine the number of columns based on screen width
          // For simplicity, let's say 2 columns if width > 600, otherwise 1 column
          final bool isLargeScreen = constraints.maxWidth > 600;

          if (isLargeScreen) {
            // Two columns layout for large screens
            return Column(
              children: [
                Row(
                  children: [
                    Expanded(child: GridCard(title: 'Item 1')),
                    Expanded(child: GridCard(title: 'Item 2')),
                  ],
                ),
                Row(
                  children: [
                    Expanded(child: GridCard(title: 'Item 3')),
                    Expanded(child: GridCard(title: 'Item 4')),
                  ],
                ),
                // Add more rows as needed
              ],
            );
          } else {
            // Single column layout for smaller screens
            return Column(
              children: [
                GridCard(title: 'Item A'),
                GridCard(title: 'Item B'),
                GridCard(title: 'Item C'),
                GridCard(title: 'Item D'),
                // Add more single items as needed
              ],
            );
          }
        },
      ),
    );
  }
}

class GridCard extends StatelessWidget {
  final String title;

  const GridCard({Key? key, required this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(8.0),
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            Icon(Icons.star, size: 40, color: Colors.amber),
            SizedBox(height: 8),
            Text(title, style: TextStyle(fontSize: 18)),
            SizedBox(height: 4),
            Text('This is a responsive grid item.', textAlign: TextAlign.center),
          ],
        ),
      ),
    );
  }
}

In this example, when the screen is considered "large," Row widgets are used, and within each Row, Expanded widgets ensure that the GridCards equally divide the horizontal space. On smaller screens, the items are simply stacked in a Column, naturally taking full width (or their intrinsic width if smaller) because they are not constrained by a Row with Expanded children.

For scenarios where one item needs a fixed width and another needs to take the remaining space, Expanded is also ideal:


Row(
  children: [
    Container(
      width: 100, // Fixed width
      color: Colors.green,
      child: Center(child: Text('Fixed')),
    ),
    Expanded( // Takes the remaining space
      child: Container(
        color: Colors.purple,
        child: Center(child: Text('Expanded Content')),
      ),
    ),
  ],
)

Key Takeaways and Best Practices

  • Expanded vs. Flexible: Use Expanded when you want a child to always fill all available space along the main axis. Use Flexible when you want the child to be able to fit within the available space but can also be smaller if its content allows (FlexFit.loose) or be forced to fill (FlexFit.tight, behaving like Expanded).
  • flex Property: Leverage the flex property to distribute space proportionally among multiple Expanded or Flexible children.
  • Nesting: Don't hesitate to nest Rows and Columns to achieve complex layouts. Expanded and Flexible work within their immediate parent Row or Column.
  • LayoutBuilder for Major Layout Changes: For significant structural changes (e.g., changing the number of columns in a grid based on screen size), combine Expanded/Flexible with LayoutBuilder or MediaQuery to dynamically render different widget trees.
  • Avoid Over-Constraining: Be mindful of placing Expanded/Flexible inside widgets that already provide infinite constraints (like ListView, or directly in a Column without an outer constrained parent that sets a fixed height) as this can lead to render errors. Always ensure they have bounded space to expand into.

Conclusion

Expanded and Flexible are indispensable widgets in a Flutter developer's toolkit for building responsive and adaptive user interfaces. By mastering their use, along with judicious application of the flex and fit properties, you can create dynamic grid layouts and other complex responsive designs that look great and function flawlessly across a multitude of devices. Embrace these widgets to unlock the full potential of Flutter's layout system.

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