Flutter Layouts & Responsive Design

Container, Padding & SizedBox

45 min Lesson 5 of 16

Understanding Container

The Container widget is one of the most versatile widgets in Flutter. It combines common painting, positioning, and sizing widgets into a single convenient widget. However, understanding when and how Container uses different render objects under the hood is key to writing efficient layouts.

Key Insight: A Container with no children tries to be as large as possible. A Container with a child sizes itself to the child. This behavior changes when you add constraints like width, height, or BoxConstraints.

When you set different properties on Container, Flutter actually composes multiple widgets internally. For example, setting color creates a ColoredBox, setting padding wraps the child in a Padding widget, setting alignment wraps it in an Align, and setting decoration uses a DecoratedBox.

Container Deep Dive

// Container with multiple properties
// Internally composes: Align > Padding > DecoratedBox > ConstrainedBox
Container(
  alignment: Alignment.center,
  padding: const EdgeInsets.all(16.0),
  margin: const EdgeInsets.symmetric(horizontal: 20.0),
  decoration: BoxDecoration(
    color: Colors.blue.shade50,
    borderRadius: BorderRadius.circular(12.0),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 8.0,
        offset: const Offset(0, 2),
      ),
    ],
  ),
  constraints: const BoxConstraints(
    minHeight: 100.0,
    maxWidth: 400.0,
  ),
  child: const Text('Hello Flutter!'),
)
Warning: You cannot set both color and decoration on a Container. If you need a background color with decoration, include the color inside the BoxDecoration using its color property.

Container Without a Child

When a Container has no child, it tries to be as big as possible, filling all available space from its parent. This is useful for creating colored backgrounds or decorative blocks.

Container Without Child

// This Container fills all available space
Scaffold(
  body: Container(
    color: Colors.blue,
    // No child - fills the entire Scaffold body
  ),
)

// This Container has a fixed size
Container(
  width: 200.0,
  height: 100.0,
  color: Colors.red,
  // No child but constrained to 200x100
)

The Padding Widget

While Container has a padding property, Flutter also provides a dedicated Padding widget. When all you need is padding around a child, using the Padding widget is more efficient and expressive than wrapping in a Container.

Padding Widget vs Container Padding

// Using Padding widget (preferred when only padding is needed)
const Padding(
  padding: EdgeInsets.all(16.0),
  child: Text('Clean and efficient'),
)

// Using Container just for padding (wasteful)
Container(
  padding: const EdgeInsets.all(16.0),
  child: const Text('Works but unnecessary overhead'),
)

// Different padding constructors
const Padding(
  padding: EdgeInsets.symmetric(
    horizontal: 24.0,
    vertical: 12.0,
  ),
  child: Text('Symmetric padding'),
)

const Padding(
  padding: EdgeInsets.only(
    left: 16.0,
    top: 8.0,
    right: 16.0,
    bottom: 24.0,
  ),
  child: Text('Directional padding'),
)

// For RTL-safe directional padding
const Padding(
  padding: EdgeInsetsDirectional.only(
    start: 16.0,
    end: 8.0,
  ),
  child: Text('RTL-aware padding'),
)
Tip: Use EdgeInsetsDirectional instead of EdgeInsets when your app supports both LTR and RTL languages. It uses start and end instead of left and right, automatically flipping for RTL layouts.

SizedBox Fundamentals

The SizedBox widget is a box with a specified size. It forces its child to have a specific width and/or height. When used without a child, it acts as an invisible spacer — one of the most common spacing patterns in Flutter.

SizedBox Basics

// Fixed-size box with a child
const SizedBox(
  width: 200.0,
  height: 100.0,
  child: Card(
    child: Center(child: Text('Fixed size card')),
  ),
)

// SizedBox as a spacer (extremely common pattern)
Column(
  children: const [
    Text('First item'),
    SizedBox(height: 16.0),  // Vertical spacing
    Text('Second item'),
    SizedBox(height: 24.0),  // Larger vertical spacing
    Text('Third item'),
  ],
)

Row(
  children: const [
    Icon(Icons.star),
    SizedBox(width: 8.0),  // Horizontal spacing
    Text('Star rating'),
  ],
)

SizedBox.expand and SizedBox.fromSize

Flutter provides convenient named constructors for common SizedBox patterns:

SizedBox Named Constructors

// SizedBox.expand - fills all available space
// Equivalent to SizedBox(width: double.infinity, height: double.infinity)
const SizedBox.expand(
  child: ColoredBox(
    color: Colors.green,
    child: Center(child: Text('I fill everything!')),
  ),
)

// SizedBox.shrink - takes minimum space (0x0)
// Useful as a placeholder or empty widget
const SizedBox.shrink()  // width: 0, height: 0

// SizedBox.fromSize - create from a Size object
SizedBox.fromSize(
  size: const Size(150.0, 75.0),
  child: const Placeholder(),
)

// SizedBox with double.infinity for one axis
const SizedBox(
  width: double.infinity,  // Full width
  height: 50.0,            // Fixed height
  child: ColoredBox(
    color: Colors.blue,
    child: Center(child: Text('Full-width bar')),
  ),
)

ConstrainedBox

While SizedBox sets an exact size, ConstrainedBox lets you define minimum and maximum constraints for width and height. This gives you more flexible sizing control.

ConstrainedBox Examples

// Set minimum dimensions
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 100.0,
    minHeight: 50.0,
  ),
  child: const Text('At least 100x50'),
)

// Set maximum dimensions
ConstrainedBox(
  constraints: const BoxConstraints(
    maxWidth: 300.0,
    maxHeight: 200.0,
  ),
  child: const Text('No larger than 300x200'),
)

// Combine min and max
ConstrainedBox(
  constraints: const BoxConstraints(
    minWidth: 100.0,
    maxWidth: 300.0,
    minHeight: 50.0,
    maxHeight: 150.0,
  ),
  child: Container(
    color: Colors.amber,
    child: const Text('Flexible within bounds'),
  ),
)

// Named constructors
ConstrainedBox(
  constraints: BoxConstraints.tight(const Size(200, 100)),
  child: const Placeholder(),  // Exactly 200x100
)

ConstrainedBox(
  constraints: BoxConstraints.loose(const Size(300, 200)),
  child: const Placeholder(),  // Up to 300x200
)

FractionallySizedBox

The FractionallySizedBox widget sizes its child to a fraction of the available space. This is incredibly useful for responsive layouts where you want elements to take a percentage of their parent’s size.

FractionallySizedBox Examples

// Take 80% of parent width and 50% of parent height
FractionallySizedBox(
  widthFactor: 0.8,
  heightFactor: 0.5,
  child: Container(
    color: Colors.purple.shade100,
    child: const Center(
      child: Text('80% width, 50% height'),
    ),
  ),
)

// Responsive container - 90% width on small screens
SizedBox(
  width: double.infinity,
  child: FractionallySizedBox(
    widthFactor: 0.9,
    child: Card(
      child: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: const [
            Text('Responsive Card'),
            SizedBox(height: 8.0),
            Text('Takes 90% of available width'),
          ],
        ),
      ),
    ),
  ),
)

// FractionallySizedBox with alignment
FractionallySizedBox(
  alignment: Alignment.topLeft,
  widthFactor: 0.6,
  heightFactor: 0.4,
  child: Container(
    color: Colors.teal,
    child: const Center(child: Text('Top-left 60%x40%')),
  ),
)

Practical Examples

Spacing Pattern: Consistent Gaps

Use SizedBox to create consistent spacing throughout your app. Many teams define spacing constants for uniformity:

Spacing Constants Pattern

// Define spacing constants
abstract class AppSpacing {
  static const double xs = 4.0;
  static const double sm = 8.0;
  static const double md = 16.0;
  static const double lg = 24.0;
  static const double xl = 32.0;
  static const double xxl = 48.0;

  // Reusable SizedBox spacers
  static const SizedBox verticalXs = SizedBox(height: xs);
  static const SizedBox verticalSm = SizedBox(height: sm);
  static const SizedBox verticalMd = SizedBox(height: md);
  static const SizedBox verticalLg = SizedBox(height: lg);
  static const SizedBox horizontalSm = SizedBox(width: sm);
  static const SizedBox horizontalMd = SizedBox(width: md);
}

// Usage in a form
Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [
    const Text('Create Account', style: TextStyle(fontSize: 24)),
    AppSpacing.verticalLg,
    const TextField(decoration: InputDecoration(labelText: 'Name')),
    AppSpacing.verticalMd,
    const TextField(decoration: InputDecoration(labelText: 'Email')),
    AppSpacing.verticalMd,
    const TextField(decoration: InputDecoration(labelText: 'Password')),
    AppSpacing.verticalLg,
    ElevatedButton(
      onPressed: () {},
      child: const Text('Sign Up'),
    ),
  ],
)
Tip: Declaring SizedBox spacers as const static fields means Flutter reuses the same widget instance everywhere, saving memory and improving performance.

Responsive Container Pattern

Responsive Content Container

class ResponsiveContainer extends StatelessWidget {
  final Widget child;
  final double maxWidth;

  const ResponsiveContainer({
    super.key,
    required this.child,
    this.maxWidth = 600.0,
  });

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: BoxConstraints(maxWidth: maxWidth),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16.0),
          child: child,
        ),
      ),
    );
  }
}

// Usage - content never exceeds 600px but fills smaller screens
Scaffold(
  body: ResponsiveContainer(
    child: Column(
      children: const [
        Text('This content is readable on all screen sizes'),
        SizedBox(height: 16),
        Text('Max width is 600px, centered on large screens'),
      ],
    ),
  ),
)
Summary: Use Container when you need multiple properties (decoration, alignment, padding) together. Use Padding when you only need padding. Use SizedBox for fixed sizes and spacing. Use ConstrainedBox for min/max constraints. Use FractionallySizedBox for percentage-based sizing.