Flutter Stack and Positioned: Overlapping Widgets Done Right

Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter Stack and Positioned guide, with layered widgets, alignment, Positioned.fill, and z-order visuals.
Coding Liquids blog cover featuring Sagnik Bhattacharya for the Flutter Stack and Positioned guide.

Rows and columns line widgets up; sometimes you need widgets to sit on top of each other instead. A badge on an avatar, a caption over an image, a floating action button above a card, a gradient scrim under text — these are all jobs for Stack and Positioned. They work like layers in an image editor: each child paints over the one before it, and you place children precisely with offsets. This guide covers layering, alignment, fit, clipping, and z-order, with runnable code throughout.

The Complete Flutter Guide course thumbnail

The Complete Flutter Guide: Build Android, iOS and Web apps

Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.

Enrol now

Every snippet is paste-ready against current stable Flutter. Stack is one of the most useful layout widgets once you know its rules, and one of the most misused before that. For the wider layout picture, this pairs with the Flutter Layout Widgets guide.

Follow me on Instagram@sagnikteaches

We will cover how a Stack sizes itself, how it aligns non-positioned children, how Positioned and Positioned.fill place layers exactly, z-order, clipping with clipBehavior, and a worked image-card example — finishing with the common mistakes.

Connect on LinkedInSagnik Bhattacharya

This tutorial is part of a series on Flutter's core widgets. Its closest companions are the Container guide and the Row and Column guide — the boxes and lines you layer here, and animating those layers is covered in the Flutter Animations guide.

Subscribe on YouTube@codingliquids

How a Stack works

A Stack paints its children in order: the first child is the bottom layer, and each later child sits on top. Children come in two kinds — non-positioned (plain widgets) and positioned (wrapped in Positioned). The Stack sizes itself to contain its non-positioned children, then aligns those children according to its alignment. Positioned children are placed by their offsets and do not affect the Stack's size.

Stack(
  children: [
    Container(width: 200, height: 200, color: Colors.indigo.shade100), // bottom layer
    const Icon(Icons.cloud, size: 64),                                  // on top, aligned
  ],
)

Aligning non-positioned children

The alignment property positions every non-positioned child within the Stack. The default is AlignmentDirectional.topStart (top-left in left-to-right locales). Set it to Alignment.center or any other alignment to move them as a group.

Stack(
  alignment: Alignment.center,
  children: [
    Container(width: 160, height: 160, color: Colors.teal.shade100),
    const Text('Centred over the box'),
  ],
)

Positioned: place a child precisely

Wrap a child in Positioned to pin it to the Stack's edges with top, right, bottom, and left. You usually set two or three of them; setting both left and right (or top and bottom) also stretches the child between those edges.

Stack(
  children: [
    Container(width: 200, height: 120, color: Colors.amber.shade200),
    Positioned(
      top: 8,
      right: 8,
      child: Container(
        padding: const EdgeInsets.all(6),
        decoration: const BoxDecoration(color: Colors.red, shape: BoxShape.circle),
        child: const Text('3', style: TextStyle(color: Colors.white)),
      ),
    ),
  ],
)

That is the classic notification badge: a box at the bottom, a small circle pinned to its top-right corner.

The Complete Flutter Guide course thumbnail

Build layered, polished UI

The Complete Flutter Guide takes you from overlays and badges to shipping real, beautiful apps on every platform.

Enrol now

Positioned.fill: stretch a layer over everything

Positioned.fill sets all four edges to zero by default, so the child fills the whole Stack. It is the cleanest way to add a full-bleed background, a gradient scrim, or a transparent tap layer.

Stack(
  children: [
    Image.network('https://picsum.photos/400/240', fit: BoxFit.cover),
    Positioned.fill(
      child: DecoratedBox(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.bottomCenter,
            end: Alignment.center,
            colors: [Colors.black54, Colors.transparent],
          ),
        ),
      ),
    ),
    const Positioned(
      left: 12,
      bottom: 12,
      child: Text('Caption', style: TextStyle(color: Colors.white, fontSize: 18)),
    ),
  ],
)

Z-order: who sits on top

There is no z-index in Flutter. Layering follows the order of the children list: earlier children are painted first and sit underneath; the last child sits on top. To bring a widget forward, move it later in the list. To send it back, move it earlier.

If you need to change which layer is on top dynamically — say, a selected card rising above its siblings — reorder the children based on state, or use an IndexedStack when you want to show only one child at a time while keeping the others alive.

Clipping with clipBehavior

By default a Stack clips anything extending past its bounds (Clip.hardEdge). Two adjustments are common:

  • clipBehavior: Clip.none lets children overflow visibly — needed when a badge or avatar should poke outside the edge.
  • Wrap the Stack in a ClipRRect to round the clip, for example to give an image card rounded corners that also clip the overlay.
Stack(
  clipBehavior: Clip.none, // let the badge overflow the top edge
  children: [
    const CircleAvatar(radius: 28, child: Icon(Icons.person)),
    Positioned(
      top: -6,
      right: -6,
      child: Container(
        padding: const EdgeInsets.all(4),
        decoration: const BoxDecoration(color: Colors.green, shape: BoxShape.circle),
        child: const Icon(Icons.check, size: 12, color: Colors.white),
      ),
    ),
  ],
)

One gotcha worth remembering: a child that overflows a Stack with Clip.none is painted, but taps outside the Stack's own bounds may not register, because hit-testing is bounded by the Stack. Keep interactive elements inside the Stack's area.

A worked example: an image card with overlay

This combines everything — a background image, a gradient scrim via Positioned.fill, a caption pinned to the bottom, and a badge in the corner — clipped to rounded corners.

ClipRRect(
  borderRadius: BorderRadius.circular(16),
  child: Stack(
    children: [
      Image.network('https://picsum.photos/400/240', width: 400, height: 240, fit: BoxFit.cover),
      Positioned.fill(
        child: DecoratedBox(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.bottomCenter,
              end: Alignment.center,
              colors: [Colors.black87, Colors.transparent],
            ),
          ),
        ),
      ),
      const Positioned(
        left: 16,
        bottom: 16,
        child: Text('Mountain trail',
            style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)),
      ),
      Positioned(
        top: 12,
        right: 12,
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
          decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20)),
          child: const Text('New'),
        ),
      ),
    ],
  ),
)

Common Stack and Positioned mistakes

  • An unsized Stack with only positioned children. If every child is Positioned, the Stack has nothing to size to and may collapse — give it a bounded size (a SizedBox or a sized first child).
  • Expecting z-index. Reorder the children list to change layering; there is no z-index property.
  • Forgetting clipBehavior: Clip.none. Badges that should overflow the edge get clipped by default.
  • Putting Positioned outside a Stack. Positioned only works as a direct child of a Stack (or Flow); elsewhere it throws.
  • Layering when a simpler widget exists. For a row of avatars or a one-at-a-time view, Wrap, Badge, or IndexedStack may read better than a hand-built Stack.

Frequently asked questions

What is a Stack in Flutter?

A layout widget that places children on top of one another in layers. The first child is at the back and each later child paints over it. Non-positioned children are aligned by the Stack; Positioned children sit at exact offsets.

What is the difference between Positioned and Positioned.fill?

Positioned places a child at specific top/right/bottom/left offsets. Positioned.fill sets all four to zero, stretching the child to cover the whole Stack — ideal for backgrounds and overlays.

How does z-order work in a Stack?

It follows child order: the first child is at the back, the last is on top. Move a widget later in the children list to bring it forward. There is no z-index.

How do I clip widgets that overflow a Stack?

A Stack clips with Clip.hardEdge by default. Use clipBehavior: Clip.none to let children overflow, or wrap the Stack in a ClipRRect for rounded clipping.

Further reads

Keep going with the tutorials that pair with this guide:

Sources: Flutter documentation — Stack, Positioned, IndexedStack, and Clip API references, plus the Layout guide (docs.flutter.dev). Verified against current stable Flutter.