Skip to content

Flexible

Understand how to create flexible widgets that can expand or shrink.


What is it?

Flexible is a widget that controls how a child of a Row, Column, or Flex flexes to fill available space. Unlike Expanded which always fills space, Flexible can be configured to either fill space (tight fit) or only take as much space as needed (loose fit). This gives you more control over flex behavior.


Why does it exist?

Flexible exists to:

  • Control flex behavior with more precision
  • Allow children to size naturally
  • Provide both tight and loose fitting
  • Create responsive layouts
  • Handle content-based sizing
  • Prevent overflow with natural sizing
  • Give more control than Expanded

Flexible vs Expanded

Flexible offers more control than Expanded.

// Flexible vs Expanded comparison
class FlexibleVsExpanded extends StatelessWidget {
  const FlexibleVsExpanded({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Expanded - always fills space
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Expanded(
                child: Container(
                  color: Colors.red,
                  child: const Center(
                    child: Text('Expanded'),
                  ),
                ),
              ),
              Expanded(
                child: Container(
                  color: Colors.blue,
                  child: const Center(
                    child: Text('Expanded'),
                  ),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 2. Flexible with tight fit (same as Expanded)
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.green,
                  child: const Center(
                    child: Text('Tight'),
                  ),
                ),
              ),
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.orange,
                  child: const Center(
                    child: Text('Tight'),
                  ),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 3. Flexible with loose fit (natural size)
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.purple,
                  child: const Text('Loose'),
                ),
              ),
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.pink,
                  child: const Text('Loose'),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

// Expanded = Flexible(fit: FlexFit.tight)
// Flexible gives more options
// Tight fills space, Loose sizes to content

What's happening here? - Expanded: always fills space - Flexible tight: same as Expanded - Flexible loose: sizes to content - More control with Flexible


FlexFit Types

Two types of fit control flexible behavior.

// FlexFit types in detail
class FlexFitTypes extends StatelessWidget {
  const FlexFitTypes({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Tight fit - Fills available space
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.red,
                  height: 50,
                  child: const Center(
                    child: Text('Tight - Fills'),
                  ),
                ),
              ),
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.blue,
                  height: 30,
                  child: const Center(
                    child: Text('Tight - Fills'),
                  ),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 2. Loose fit - Sizes to content
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.green,
                  height: 50,
                  child: const Center(
                    child: Text('Loose - Sizes'),
                  ),
                ),
              ),
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.orange,
                  height: 30,
                  child: const Center(
                    child: Text('Loose - Sizes'),
                  ),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 3. Mixed tight and loose
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.purple,
                  child: const Center(
                    child: Text('Tight'),
                  ),
                ),
              ),
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.pink,
                  child: const Text('Loose'),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

// FlexFit comparison:
// Tight: Forces child to fill space
// Loose: Allows child to size naturally
// Tight = Expanded behavior
// Loose = More flexible behavior

What's happening here? - Tight: child forced to fill - Loose: child sizes naturally - Can mix tight and loose - Choose based on need


Flexible with Content

Flexible adapts to content size.

// Flexible with varying content
class FlexibleWithContent extends StatelessWidget {
  const FlexibleWithContent({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Different text lengths with loose fit
        Container(
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.red[100],
                  padding: const EdgeInsets.all(8),
                  child: const Text('Short'),
                ),
              ),
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.green[100],
                  padding: const EdgeInsets.all(8),
                  child: const Text('Medium length text'),
                ),
              ),
              Flexible(
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.blue[100],
                  padding: const EdgeInsets.all(8),
                  child: const Text('Very long text that takes up more space'),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 2. Tight fit ignores content size
        Container(
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.red[100],
                  padding: const EdgeInsets.all(8),
                  child: const Text('Short'),
                ),
              ),
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.green[100],
                  padding: const EdgeInsets.all(8),
                  child: const Text('Medium length text'),
                ),
              ),
              Flexible(
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.blue[100],
                  padding: const EdgeInsets.all(8),
                  child: const Text('Very long text that takes up more space'),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

What's happening here? - Loose: content determines size - Tight: space determines size - Loose prevents overflow - Tight ensures equal space


Flexible with Flex Factor

Flex factors work with Flexible too.

// Flexible with flex factors
class FlexibleWithFlex extends StatelessWidget {
  const FlexibleWithFlex({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 1. Tight with different flex factors
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                flex: 2,
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.red,
                  child: const Center(child: Text('2')),
                ),
              ),
              Flexible(
                flex: 1,
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.blue,
                  child: const Center(child: Text('1')),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 2. Loose with different flex factors
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                flex: 2,
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.green,
                  child: const Text('Flex 2 Loose'),
                ),
              ),
              Flexible(
                flex: 1,
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.orange,
                  child: const Text('Flex 1 Loose'),
                ),
              ),
            ],
          ),
        ),

        const SizedBox(height: 10),

        // 3. Mixed flex and fit
        Container(
          height: 80,
          color: Colors.grey[200],
          child: Row(
            children: [
              Flexible(
                flex: 2,
                fit: FlexFit.tight,
                child: Container(
                  color: Colors.purple,
                  child: const Center(child: Text('Tight 2')),
                ),
              ),
              Flexible(
                flex: 1,
                fit: FlexFit.loose,
                child: Container(
                  color: Colors.pink,
                  child: const Text('Loose 1'),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

What's happening here? - Flex factors work with Flexible - Can combine with fit types - More control over space - Flexible and proportional


Real-World Examples

Common patterns using Flexible.

// 1. Responsive text layout
class ResponsiveTextLayout extends StatelessWidget {
  const ResponsiveTextLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      color: Colors.grey[200],
      child: Row(
        children: [
          // Label takes as much as needed
          Flexible(
            fit: FlexFit.loose,
            child: Text(
              'Label:',
              style: const TextStyle(
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          const SizedBox(width: 8),
          // Value takes remaining space if needed
          Flexible(
            fit: FlexFit.tight,
            child: Text(
              'This is a very long value that might need more space',
              overflow: TextOverflow.ellipsis,
            ),
          ),
        ],
      ),
    );
  }
}

// 2. Icon with text
class IconWithText extends StatelessWidget {
  const IconWithText({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      color: Colors.grey[200],
      child: Row(
        children: [
          const Icon(Icons.star, size: 24),
          const SizedBox(width: 8),
          // Text takes remaining space but wraps if needed
          Flexible(
            fit: FlexFit.loose,
            child: Text(
              'This is a very long text that might need to wrap',
              softWrap: true,
            ),
          ),
        ],
      ),
    );
  }
}

// 3. Dynamic chip layout
class DynamicChipLayout extends StatelessWidget {
  const DynamicChipLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      color: Colors.grey[200],
      child: Wrap(
        spacing: 8,
        runSpacing: 8,
        children: [
          'Flutter', 'Dart', 'Mobile', 'Web', 'Desktop',
          'iOS', 'Android', 'Development', 'UI', 'UX'
        ].map((label) {
          return Flexible(
            fit: FlexFit.loose,
            child: Chip(
              label: Text(label),
              backgroundColor: Colors.blue[100],
            ),
          );
        }).toList(),
      ),
    );
  }
}

// 4. Flexible form layout
class FlexibleFormLayout extends StatelessWidget {
  const FlexibleFormLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          // Label and input on same line
          Row(
            children: [
              Flexible(
                fit: FlexFit.loose,
                child: const Text(
                  'Name:',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
              ),
              const SizedBox(width: 8),
              Flexible(
                fit: FlexFit.tight,
                child: const TextField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    contentPadding: EdgeInsets.symmetric(horizontal: 8),
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          // Another row with flexible input
          Row(
            children: [
              Flexible(
                fit: FlexFit.loose,
                child: const Text(
                  'Email:',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
              ),
              const SizedBox(width: 8),
              Flexible(
                fit: FlexFit.tight,
                child: const TextField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    contentPadding: EdgeInsets.symmetric(horizontal: 8),
                  ),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

What's happening here? - Label and value with flexible spacing - Icon with wrapping text - Dynamic chip layout - Flexible form rows


Best Practices

Use Flexible for Natural Sizing

// Good - Loose fit for natural sizing
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      Flexible(
        fit: FlexFit.loose,
        child: Text('Natural size'),
      ),
    ],
  );
}

Use Flexible to Prevent Overflow

// Good - Prevents overflow
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      const Icon(Icons.star),
      const SizedBox(width: 8),
      Flexible(
        child: Text(
          'Long text that might overflow',
          overflow: TextOverflow.ellipsis,
        ),
      ),
    ],
  );
}

Choose Appropriate Fit

// Good - Choose based on need
@override
Widget build(BuildContext context) {
  return Row(
    children: [
      // Tight: fill space
      Flexible(
        fit: FlexFit.tight,
        child: Container(color: Colors.red),
      ),
      // Loose: size naturally
      Flexible(
        fit: FlexFit.loose,
        child: Text('Content'),
      ),
    ],
  );
}

Common Mistakes

Using Flexible When Expanded Works

Wrong:

// Overcomplicating
Flexible(
  fit: FlexFit.tight,
  child: Container(color: Colors.blue),
)

Correct:

// Simpler
Expanded(
  child: Container(color: Colors.blue),
)

Not Choosing Fit Type

Wrong:

// Default is tight (same as Expanded)
Flexible(
  child: Text('Default tight'),
)

Correct:

// Explicit choice
Flexible(
  fit: FlexFit.loose,
  child: Text('Loose fit'),
)


Summary

Flexible provides more control than Expanded with tight and loose fit options. Tight fit forces filling space (like Expanded), while loose fit allows natural sizing. Use Flexible when you need content-based sizing or prevent overflow.


Next Steps


Did You Know?

  • Flexible tight = Expanded
  • Flexible loose allows natural sizing
  • Flex factors work with Flexible
  • Flexible helps prevent overflow
  • Flexible is more flexible than Expanded
  • Choose fit based on needs
  • Flexible can be used with any child
  • Flexible is often better for text