Skip to content

Flex Layout

Understand how flex layout works in Flutter for responsive designs.


What is it?

Flex layout is a system for arranging widgets in a flexible, responsive way. It allows widgets to expand, shrink, and distribute space within a container. The flex layout in Flutter uses Row and Column widgets with Expanded and Flexible children to create adaptive layouts that respond to different screen sizes.


Why does it exist?

Flex layout exists to:

  • Create responsive layouts that adapt to screen size
  • Distribute space evenly between widgets
  • Allow widgets to expand and shrink flexibly
  • Support complex UI arrangements
  • Handle different device orientations
  • Simplify responsive design
  • Enable dynamic and adaptive interfaces

Flex Basics

Row and Column are the basic flex widgets.

// Basic flex layout
class BasicFlex extends StatelessWidget {
  const BasicFlex({super.key});

  @override
  Widget build(BuildContext context) {
    // Row - horizontal flex
    return Row(
      children: [
        Container(width: 50, color: Colors.red),
        Container(width: 50, color: Colors.green),
        Container(width: 50, color: Colors.blue),
      ],
    );
  }
}

// Column - vertical flex
class BasicColumn extends StatelessWidget {
  const BasicColumn({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(height: 50, color: Colors.red),
        Container(height: 50, color: Colors.green),
        Container(height: 50, color: Colors.blue),
      ],
    );
  }
}

// Flex properties:
// 1. direction - Axis.horizontal or Axis.vertical
// 2. mainAxisAlignment - How to align along main axis
// 3. crossAxisAlignment - How to align along cross axis
// 4. children - List of widgets
// 5. mainAxisSize - Min or max space

What's happening here? - Row arranges children horizontally - Column arranges children vertically - Children are placed in order - Main axis is direction of layout - Cross axis is perpendicular


MainAxisAlignment

MainAxisAlignment controls alignment along the main axis.

// MainAxisAlignment options
class MainAxisAlignmentExample extends StatelessWidget {
  const MainAxisAlignmentExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Start - Align to start
        Row(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Container(width: 50, height: 50, color: Colors.red),
            Container(width: 50, height: 50, color: Colors.green),
          ],
        ),

        // 2. Center - Align to center
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(width: 50, height: 50, color: Colors.red),
            Container(width: 50, height: 50, color: Colors.green),
          ],
        ),

        // 3. End - Align to end
        Row(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Container(width: 50, height: 50, color: Colors.red),
            Container(width: 50, height: 50, color: Colors.green),
          ],
        ),

        // 4. SpaceBetween - Space between children
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Container(width: 50, height: 50, color: Colors.red),
            Container(width: 50, height: 50, color: Colors.green),
            Container(width: 50, height: 50, color: Colors.blue),
          ],
        ),

        // 5. SpaceAround - Space around children
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Container(width: 50, height: 50, color: Colors.red),
            Container(width: 50, height: 50, color: Colors.green),
            Container(width: 50, height: 50, color: Colors.blue),
          ],
        ),

        // 6. SpaceEvenly - Even space between and around
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Container(width: 50, height: 50, color: Colors.red),
            Container(width: 50, height: 50, color: Colors.green),
            Container(width: 50, height: 50, color: Colors.blue),
          ],
        ),
      ],
    );
  }
}

// Visual comparison:
// start:    [1][2]
// center:     [1][2]
// end:       [1][2]
// spaceBetween: [1]   [2]   [3]
// spaceAround:   [1]   [2]   [3]
// spaceEvenly:   [1]   [2]   [3]

What's happening here? - start aligns children to beginning - center centers children - end aligns children to end - spaceBetween puts space between children - spaceAround puts space around children - spaceEvenly distributes space evenly


CrossAxisAlignment

CrossAxisAlignment controls alignment along the cross axis.

// CrossAxisAlignment options
class CrossAxisAlignmentExample extends StatelessWidget {
  const CrossAxisAlignmentExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 200,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(width: 50, height: 50, color: Colors.red),
          Container(width: 50, height: 100, color: Colors.green),
          Container(width: 50, height: 75, color: Colors.blue),
        ],
      ),
    );
  }
}

// CrossAxisAlignment options:
// 1. CrossAxisAlignment.start - Align to top
// 2. CrossAxisAlignment.center - Align to center
// 3. CrossAxisAlignment.end - Align to bottom
// 4. CrossAxisAlignment.stretch - Stretch to fill
// 5. CrossAxisAlignment.baseline - Align by text baseline

// Example with different cross alignments:
class CrossAlignmentComparison extends StatelessWidget {
  const CrossAlignmentComparison({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Start
        Container(
          height: 100,
          color: Colors.grey[200],
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(width: 50, height: 30, color: Colors.red),
              Container(width: 50, height: 50, color: Colors.green),
              Container(width: 50, height: 40, color: Colors.blue),
            ],
          ),
        ),

        // 2. Center
        Container(
          height: 100,
          color: Colors.grey[200],
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Container(width: 50, height: 30, color: Colors.red),
              Container(width: 50, height: 50, color: Colors.green),
              Container(width: 50, height: 40, color: Colors.blue),
            ],
          ),
        ),

        // 3. End
        Container(
          height: 100,
          color: Colors.grey[200],
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.end,
            children: [
              Container(width: 50, height: 30, color: Colors.red),
              Container(width: 50, height: 50, color: Colors.green),
              Container(width: 50, height: 40, color: Colors.blue),
            ],
          ),
        ),

        // 4. Stretch
        Container(
          height: 100,
          color: Colors.grey[200],
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Container(width: 50, color: Colors.red),
              Container(width: 50, color: Colors.green),
              Container(width: 50, color: Colors.blue),
            ],
          ),
        ),
      ],
    );
  }
}

What's happening here? - start aligns to beginning of cross axis - center centers on cross axis - end aligns to end of cross axis - stretch fills cross axis space - baseline aligns by text baseline


Expanded and Flexible

Expanded and Flexible control how children take space.

// Expanded - Takes all available space
class ExpandedExample extends StatelessWidget {
  const ExpandedExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // Fixed width
        Container(
          width: 50,
          color: Colors.red,
          child: const Text('Fixed'),
        ),

        // Takes remaining space
        Expanded(
          child: Container(
            color: Colors.blue,
            child: const Text('Expanded'),
          ),
        ),

        // Also takes remaining space
        Expanded(
          child: Container(
            color: Colors.green,
            child: const Text('Expanded'),
          ),
        ),
      ],
    );
  }
}

// Flexible - Flexible with limits
class FlexibleExample extends StatelessWidget {
  const FlexibleExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        // Flexible with tight fit
        Flexible(
          fit: FlexFit.tight,
          child: Container(
            color: Colors.red,
            child: const Text('Tight'),
          ),
        ),

        // Flexible with loose fit
        Flexible(
          fit: FlexFit.loose,
          child: Container(
            color: Colors.blue,
            child: const Text('Loose'),
          ),
        ),
      ],
    );
  }
}

// Expanded vs Flexible:
// Expanded: Always takes all available space
// Flexible: Can take space, but has limits
// Expanded = Flexible(fit: FlexFit.tight)

What's happening here? - Expanded forces child to fill remaining space - Flexible allows child to flex but has limits - Tight fit forces child to fill - Loose fit allows child to size itself - Expanded is commonly used for responsive layouts


Flex Factors

Flex factors control proportional space distribution.

// Flex factors example
class FlexFactorsExample extends StatelessWidget {
  const FlexFactorsExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Equal flex factors (1:1:1)
        Row(
          children: [
            Expanded(
              flex: 1,
              child: Container(
                color: Colors.red,
                height: 50,
                child: const Center(child: Text('1')),
              ),
            ),
            Expanded(
              flex: 1,
              child: Container(
                color: Colors.green,
                height: 50,
                child: const Center(child: Text('1')),
              ),
            ),
            Expanded(
              flex: 1,
              child: Container(
                color: Colors.blue,
                height: 50,
                child: const Center(child: Text('1')),
              ),
            ),
          ],
        ),

        const SizedBox(height: 10),

        // Different flex factors (1:2:3)
        Row(
          children: [
            Expanded(
              flex: 1,
              child: Container(
                color: Colors.red,
                height: 50,
                child: const Center(child: Text('1')),
              ),
            ),
            Expanded(
              flex: 2,
              child: Container(
                color: Colors.green,
                height: 50,
                child: const Center(child: Text('2')),
              ),
            ),
            Expanded(
              flex: 3,
              child: Container(
                color: Colors.blue,
                height: 50,
                child: const Center(child: Text('3')),
              ),
            ),
          ],
        ),

        const SizedBox(height: 10),

        // Mixed fixed and flexible
        Row(
          children: [
            Container(
              width: 80,
              color: Colors.red,
              height: 50,
              child: const Center(child: Text('Fixed')),
            ),
            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')),
              ),
            ),
          ],
        ),
      ],
    );
  }
}

// Flex factor calculation:
// Total flex = sum of all flex factors
// Space per flex = available space / total flex
// Child space = flex factor * space per flex

// Example: flex factors 1,2,3
// Total flex = 6
// Space per flex = 100% / 6 = 16.67%
// Child 1 = 16.67%
// Child 2 = 33.33%
// Child 3 = 50%

What's happening here? - Flex factors distribute space proportionally - Higher flex = more space - Total flex is sum of all factors - Available space divided by total flex - Children get space proportionally


Intrinsic Sizing

Intrinsic sizing constrains children naturally.

// Intrinsic sizing examples
class IntrinsicSizingExample extends StatelessWidget {
  const IntrinsicSizingExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Without intrinsic sizing
        Container(
          color: Colors.grey[200],
          child: Row(
            children: [
              Container(
                width: 100,
                height: 100,
                color: Colors.red,
                child: const Text('Fixed'),
              ),
              Container(
                color: Colors.blue,
                child: const Text(
                  'Long text that determines width',
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // With intrinsic sizing
        Container(
          color: Colors.grey[200],
          child: Row(
            children: [
              IntrinsicWidth(
                child: Container(
                  height: 100,
                  color: Colors.red,
                  child: const Text('Fixed'),
                ),
              ),
              Expanded(
                child: Container(
                  color: Colors.blue,
                  child: const Text(
                    'Long text that determines width',
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

// Intrinsic widgets:
// 1. IntrinsicWidth - Sizes to child's intrinsic width
// 2. IntrinsicHeight - Sizes to child's intrinsic height
// 3. IntrinsicDimension - Custom intrinsic sizing

// Use cases:
// 1. When children have different intrinsic sizes
// 2. When you want children to be sized by content
// 3. When you need consistent sizing

What's happening here? - IntrinsicWidth sizes to content width - IntrinsicHeight sizes to content height - Useful for consistent sizing - Can affect performance with complex layouts


Responsive Flex Layouts

Making flex layouts responsive to screen size.

// Responsive flex layout
class ResponsiveFlexLayout extends StatelessWidget {
  const ResponsiveFlexLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        // 1. Check available space
        final isWide = constraints.maxWidth > 600;

        // 2. Choose layout based on width
        if (isWide) {
          // Wide layout - row
          return Row(
            children: [
              Expanded(
                flex: 2,
                child: Container(
                  color: Colors.blue,
                  padding: const EdgeInsets.all(20),
                  child: const Text('Sidebar'),
                ),
              ),
              Expanded(
                flex: 5,
                child: Container(
                  color: Colors.grey[200],
                  padding: const EdgeInsets.all(20),
                  child: const Text('Content'),
                ),
              ),
            ],
          );
        } else {
          // Narrow layout - column
          return Column(
            children: [
              Container(
                width: double.infinity,
                color: Colors.blue,
                padding: const EdgeInsets.all(20),
                child: const Text('Header'),
              ),
              Expanded(
                child: Container(
                  width: double.infinity,
                  color: Colors.grey[200],
                  padding: const EdgeInsets.all(20),
                  child: const Text('Content'),
                ),
              ),
            ],
          );
        }
      },
    );
  }
}

// Adaptive flex layout
class AdaptiveFlexLayout extends StatelessWidget {
  const AdaptiveFlexLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Wrap for adaptive sizing
        Wrap(
          spacing: 8,
          runSpacing: 8,
          children: [
            _buildChip('Android'),
            _buildChip('iOS'),
            _buildChip('Web'),
            _buildChip('Desktop'),
            _buildChip('Mobile'),
            _buildChip('Flutter'),
          ],
        ),

        const SizedBox(height: 20),

        // Flexible grid
        LayoutBuilder(
          builder: (context, constraints) {
            final crossAxisCount = (constraints.maxWidth / 150).floor();
            return GridView.builder(
              shrinkWrap: true,
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: crossAxisCount.clamp(2, 4),
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
              ),
              itemCount: 6,
              itemBuilder: (context, index) {
                return Container(
                  color: Colors.blue[100 * (index + 1)],
                  child: Center(
                    child: Text('Item $index'),
                  ),
                );
              },
            );
          },
        ),
      ],
    );
  }

  Widget _buildChip(String label) {
    return Chip(
      label: Text(label),
      backgroundColor: Colors.blue[100],
    );
  }
}

What's happening here? - LayoutBuilder for responsive decisions - Wrap for adaptive content - Dynamic grid based on width - Different layouts for different screen sizes - Flexible and adaptive design


Best Practices

Use Appropriate Alignment

// Good - Using alignment
@override
Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      Icon(Icons.home),
      Icon(Icons.search),
      Icon(Icons.person),
    ],
  );
}

// Bad - Using hardcoded spacing
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      Icon(Icons.home),
      const SizedBox(width: 20),
      Icon(Icons.search),
      const SizedBox(width: 20),
      Icon(Icons.person),
    ],
  );
}

Use Expanded Wisely

// Good - Using Expanded
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      const Icon(Icons.star),
      const SizedBox(width: 8),
      Expanded(
        child: Text('Very long text that will expand'),
      ),
    ],
  );
}

// Bad - Not using Expanded
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      const Icon(Icons.star),
      const SizedBox(width: 8),
      Text('Very long text that might overflow'), // Could overflow
    ],
  );
}

Keep Flex Trees Shallow

// Good - Shallow flex tree
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      Expanded(child: Child1()),
      Expanded(child: Child2()),
      Expanded(child: Child3()),
    ],
  );
}

// Bad - Deep flex tree
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      Row(
        children: [
          Column(
            children: [
              Row(
                children: [
                  // Very deep
                ],
              ),
            ],
          ),
        ],
      ),
    ],
  );
}

Common Mistakes

Overusing Expanded

Wrong:

// Every child expanded
Row(
  children: [
    Expanded(child: Container(color: Colors.red)),
    Expanded(child: Container(color: Colors.green)),
    Expanded(child: Container(color: Colors.blue)),
  ],
)

Correct:

// Use flex factors for proportional sizes
Row(
  children: [
    Expanded(
      flex: 1,
      child: Container(color: Colors.red),
    ),
    Expanded(
      flex: 2,
      child: Container(color: Colors.green),
    ),
    Expanded(
      flex: 3,
      child: Container(color: Colors.blue),
    ),
  ],
)

Wrong CrossAxisAlignment

Wrong:

// Row with unexpected heights
Row(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Container(height: 50, color: Colors.red),
    Container(height: 100, color: Colors.green), // Different height
    Container(height: 30, color: Colors.blue),
  ],
)

Correct:

// Consistent heights or use stretch
Row(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [
    Container(width: 50, color: Colors.red),
    Container(width: 50, color: Colors.green),
    Container(width: 50, color: Colors.blue),
  ],
)


Summary

Flex layout in Flutter uses Row and Column widgets with Expanded and Flexible children to create responsive layouts. Understanding mainAxisAlignment, crossAxisAlignment, and flex factors helps build adaptive designs. Use flex layouts for dynamic, responsive UIs.


Next Steps


Did You Know?

  • Row and Column are both Flex widgets
  • Expanded forces children to fill available space
  • Flex factors distribute space proportionally
  • CrossAxisAlignment controls vertical alignment in Row
  • MainAxisAlignment controls horizontal alignment in Row
  • Wrap is useful for responsive chip layouts
  • Flex layouts are used in most UI designs
  • Intrinsic sizing can optimize layouts