Skip to content

Container in Flutter: The Box Layout Widget Explained

Master Container in Flutter. Width, height, padding, margin, BoxDecoration: color, gradient, border, radius, shadow. Plus constraint pitfalls.

· · 7 min read
Flutter UI mockup showing nested Container widgets with rounded corners gradient backgrounds and shadow effects

Quick Take

Container is the Swiss army knife of Flutter layout. It handles padding, margins, colour, borders, gradients, shadows, and constraints in one widget. This tutorial covers every property you'll actually use, with the constraint-resolution gotcha that catches every new Flutter developer.

Quick take: Container is Flutter's general-purpose layout box. It combines width, height, padding, margin, alignment, and BoxDecoration in one widget. Use it for backgrounds, borders, rounded corners, gradients, and shadows. Reach for SizedBox if you only need sizing or Padding if you only need spacing. The most common pitfall: setting both color: and decoration: properties throws an assertion at runtime.

Container is the widget every Flutter tutorial reaches for first, and for good reason. It bundles a handful of single-purpose widgets (Padding, DecoratedBox, ConstrainedBox, Align) into one builder, so a UI box with padding, a coloured background, rounded corners, and a shadow takes one widget instead of four. That's convenience. The cost is that Container is the most often-overused widget in any Flutter codebase, including mine.

This tutorial covers the properties that come up day to day, the BoxDecoration sub-properties that handle visuals, the constraint behaviour that confuses everyone at first, and when to reach for a smaller widget instead.

What Does a Container Actually Do?

Container is a composition of smaller widgets. From the Flutter source, when you write:

Container(
  padding: const EdgeInsets.all(16),
  margin: const EdgeInsets.all(8),
  decoration: BoxDecoration(color: Colors.blue),
  width: 200,
  height: 100,
  child: const Text('Hello'),
)

The framework expands that to roughly:

Padding(
  padding: const EdgeInsets.all(8),
  child: DecoratedBox(
    decoration: BoxDecoration(color: Colors.blue),
    child: ConstrainedBox(
      constraints: const BoxConstraints.tightFor(width: 200, height: 100),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: const Text('Hello'),
      ),
    ),
  ),
)

Same render result, more readable as Container. The Flutter docs even mention this composition explicitly. Knowing the expansion matters when debugging: if you see weird intermediate widgets in the inspector, it's because Container created them.

What Are the Sizing Properties?

The basics:

  • width: a double in logical pixels, or double.infinity to fill available width
  • height: same, vertical
  • constraints: a BoxConstraints for finer control (minWidth, maxWidth, minHeight, maxHeight)

A few examples:

// Fixed size
Container(width: 100, height: 100, color: Colors.red)

// Fill the parent's available width, fixed height
Container(width: double.infinity, height: 50, color: Colors.blue)

// Constrained to a max width but flexible
Container(
  constraints: const BoxConstraints(maxWidth: 400),
  child: const Text('A long line of text that will wrap at 400px'),
)

The constraint gotcha: double.infinity only works if the parent provides bounded constraints in that axis. Inside a Column, the horizontal axis has bounded width (the column's width), so width: double.infinity is fine. The vertical axis is unbounded by default, so height: double.infinity throws. Inside a Scaffold's body, both axes are bounded, so either is fine.

If you've ever seen "RenderFlex children have non-zero flex but incoming height constraints are unbounded", that's the constraint rule biting. The fix is usually wrapping in Expanded (inside Row/Column) or setting a finite size on a parent.

Stacked shipping containers of many colors under a blue sky
Photo by Getty Images on Unsplash

How Does padding vs margin Work?

Same as CSS:

  • padding: space inside the Container, between its visual edge and the child
  • margin: space outside the Container, between its visual edge and siblings
Container(
  margin: const EdgeInsets.all(20),    // 20px gap outside
  padding: const EdgeInsets.all(10),   // 10px gap inside
  color: Colors.green,
  child: const Text('Hello'),
)

The green background extends to the padding edge but not into the margin. If you want background coverage all the way to the parent's edge, drop the margin and let the parent handle spacing.

EdgeInsets has several constructors:

EdgeInsets.all(16)                                  // 16 on every side
EdgeInsets.symmetric(horizontal: 16, vertical: 8)   // L+R = 16, T+B = 8
EdgeInsets.only(left: 16, top: 8)                   // Specific sides
EdgeInsets.fromLTRB(16, 8, 16, 8)                   // L T R B

I use symmetric more than the others, most real layouts have different horizontal and vertical padding.

How Does BoxDecoration Work?

BoxDecoration is where visuals happen, colour, gradient, border, border radius, shadow, background image. The full constructor:

Container(
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(12),
    border: Border.all(color: Colors.black, width: 2),
    boxShadow: [
      BoxShadow(
        color: Colors.black26,
        blurRadius: 8,
        offset: const Offset(0, 4),
      ),
    ],
  ),
  width: 200,
  height: 100,
)

That single widget gets you a blue rounded card with a black border and a soft drop shadow. Same effect in CSS would be background: blue; border-radius: 12px; border: 2px solid black; box-shadow: 0 4px 8px rgba(0,0,0,0.26), the mental model translates directly.

Gradients

For a gradient background, swap color for gradient:

Container(
  decoration: const BoxDecoration(
    gradient: LinearGradient(
      colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
    ),
  ),
  width: 200,
  height: 100,
)

LinearGradient covers most needs. RadialGradient and SweepGradient exist for circle and pie-slice patterns. The colors list can have any number of stops, with optional stops: to control distribution.

Border Radius Variants

Four common border radius patterns:

// Uniform on all corners
BorderRadius.circular(12)

// Different per corner
const BorderRadius.only(
  topLeft: Radius.circular(16),
  topRight: Radius.circular(16),
)

// All round
BorderRadius.circular(999)   // pill shape on a fixed-size box

// Horizontal vs vertical
const BorderRadius.vertical(top: Radius.circular(12))

BorderRadius.circular(999) on a square Container is the classic way to get a circle. Or use a CircleAvatar widget if you specifically want a circular image, that's purpose-built for the use case.

Shadows

The boxShadow property takes a list, so you can stack multiple shadows for depth:

boxShadow: [
  BoxShadow(
    color: Colors.black12,
    blurRadius: 4,
    offset: const Offset(0, 2),
  ),
  BoxShadow(
    color: Colors.black12,
    blurRadius: 16,
    offset: const Offset(0, 8),
  ),
],

Two shadows, one tight and close, one diffuse and far. That's the Material Design elevation recipe in two BoxShadow entries.

The color vs decoration Rule

This catches every new Flutter dev. You can set color: directly on Container, or you can set decoration:. But not both:

// Throws an assertion at runtime
Container(
  color: Colors.blue,
  decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
)

The fix: move the colour into the decoration:

Container(
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(8),
  ),
)

The Container constructor has an assert(color == null || decoration == null) that explicitly bans the combination, to prevent ambiguity about which one applies. Honestly, I think this is a poor API choice, every new Flutter dev trips on it. But the rule's been stable since 1.0 and won't change.

When Should You NOT Use a Container?

Three cases where a smaller widget reads better:

  1. Need only a fixed size? Use SizedBox(width: 100, height: 50). No decoration, no padding, less indirection.
  2. Need only padding? Use Padding(padding: EdgeInsets.all(16), child: ...). Same.
  3. Need only a coloured background? Use ColoredBox(color: Colors.blue, child: ...). Faster than Container.

In a real codebase, you'll still reach for Container often, the moment you need two of those features it wins. But the inspector tree shows up cleaner if you use the specialised widget for single-concern cases.

A wall of blue, red, green, and yellow intermodal containers stacked in a grid
Photo by Paul .T on Unsplash

How Do You Animate a Container?

AnimatedContainer is Container's drop-in animated cousin. Same properties, plus a duration and an optional curve:

class FadingBox extends StatefulWidget {
  const FadingBox({super.key});

  @override
  State<FadingBox> createState() => _FadingBoxState();
}

class _FadingBoxState extends State<FadingBox> {
  bool _toggled = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _toggled = !_toggled),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 300),
        curve: Curves.easeInOut,
        width: _toggled ? 200 : 100,
        height: 100,
        decoration: BoxDecoration(
          color: _toggled ? Colors.purple : Colors.blue,
          borderRadius: BorderRadius.circular(_toggled ? 24 : 8),
        ),
      ),
    );
  }
}

Tap to toggle, and the width, colour, and border radius all animate over 300ms. No AnimationController, no Tween, no listeners. For state-driven UI changes, AnimatedContainer is the lowest-effort option in the framework and covers maybe 70% of "I want this to animate" cases.

What Should You Build with Container?

Card layouts, list item rows, settings tiles, hero sections, gradient buttons, image overlays, custom badges. Anything that needs a styled box around content. Once Container clicks, you'll see it everywhere in real Flutter code, sometimes overused, sometimes correctly.

If you're starting out with Flutter, build a single card UI with a profile image (use a network ImageView equivalent, Image.network), a name, a description, and a few action buttons. Wrap each section in Container with appropriate padding and decoration. That's the building block for 80% of mobile UIs.

Frequently Asked Questions

When should I use Container vs SizedBox vs Padding?
SizedBox if you only need fixed width or height. Padding if you only need to add padding around a child. Container only when you need two or more of: padding, margin, decoration, alignment. Container builds a chain of smaller widgets under the hood, so using it where SizedBox would do adds unnecessary tree depth.
What's the difference between padding and margin in Container?
padding is space inside the Container, between its border and the child. margin is space outside the Container, between its border and surrounding widgets. Same as CSS. If you set a background color, padding fills with the color and margin stays transparent.
Why is my Container's color ignored?
Because you set both color: and decoration: on the same Container. The Container constructor explicitly forbids that combination and throws an assertion at runtime. Move the color into the decoration: BoxDecoration(color: Colors.blue, ...) instead. Personally I find this rule annoying, but it's there to prevent ambiguity.
How do I make a Container fill the available space?
Wrap it in an Expanded if it's inside a Row or Column. Use double.infinity for width or height if it's inside a parent that provides bounded constraints (Scaffold body, Container with fixed size). Without bounded constraints from the parent, double.infinity throws a layout error, which is the constraint gotcha that catches every new Flutter dev.
Can I animate a Container?
Use AnimatedContainer instead. Same API as Container, plus a duration and an optional curve. When any property changes, Flutter tweens between the old and new values over the duration. Great for state-driven colour changes, size changes, and decoration tweaks without writing AnimationController code.