All Guides
    Flutter Basics
    Flutter Basics
    Layouts
    Overlay

    Stack and Positioned Widgets

    Published April 2026

    Introduction

    In the standard Flutter layout system, widgets are arranged linearly using Row and Column layout grids. While this flow-based system covers most user interface scenarios, you will inevitably need to overlay components on top of each other. Common examples include placing a badge notification count on top of an icon, overlaying text on top of an image card, or creating a profile cover screen with a floating avatar image.

    To achieve overlapping layouts, Flutter provides the Stack and Positioned widgets. Rather than arranging children sequentially along a linear path, a Stack layers its children on top of each other. The order of rendering is from bottom to top; the first widget declared in the children list is placed at the bottom, and subsequent widgets are painted on top of it.

    Understanding how stacks handle sizing and coordinates is crucial. Unlike rows and columns that operate on logical flex factors, Stacks rely on constraints and absolute layout coordinates. In this guide, you will learn the theory of stack overlay alignments, review positioning parameters, and build a beautiful, compiler-valid profile banner.


    Prerequisites

    Before building overlapping layouts, ensure you have:


    Core Explanation: Overlapping Layers & Coordinates

    A Stack widget takes a list of children and layers them. The sizing and layout positioning of these children depend on whether they are Positioned or Non-positioned.

    ┌──────────────────────────────────────────┐
    │              STACK VIEWPORT              │
    │  (0,0)                                   │
    │  ┌──────────────────────────────┐        │
    │  │ Base Widget (Non-Positioned)  │        │
    │  │                              │        │
    │  │         (Positioned)         │        │
    │  │         ┌────────────┐       │        │
    │  │         │ top: 40    │       │        │
    │  │         │ left: 80   │       │        │
    │  │         └────────────┘       │        │
    │  └──────────────────────────────┘        │
    └──────────────────────────────────────────┘
    

    1. Non-Positioned Children

    A child widget is "non-positioned" if it is not wrapped in a Positioned widget. By default, the Stack sizes itself to fit the dimensions of its largest non-positioned child. All non-positioned children are aligned inside the stack boundary using the alignment property (e.g. Alignment.center, Alignment.bottomRight).

    2. Positioned Children

    A child widget is "positioned" when wrapped in a Positioned widget. The Positioned widget allows you to define absolute distances from the borders of the stack container using four properties:

    • top: Distance from the top edge.
    • bottom: Distance from the bottom edge.
    • left: Distance from the left edge.
    • right: Distance from the right edge.

    If you specify both left and right coordinates on a widget, the widget is forced to stretch horizontally to satisfy both parameters. The same stretching logic applies vertically if both top and bottom are set.

    3. Sizing Constraints: StackFit

    The fit property of the Stack controls how parent constraints are passed to its children:

    • StackFit.loose: Children are allowed to be any size within the parent's maximum constraints (default).
    • StackFit.expand: Forces all non-positioned children to expand and fill the maximum allowed dimensions.
    • StackFit.passthrough: Passes the parent constraints directly to the children without modification.

    Practical Example: Composing a Profile Header Banner

    Let's build a clean, compiler-ready Profile Header containing a background cover photo, an overlapping user avatar, and a floating online status badge.

    import 'package:flutter/material.dart';
    
    /// A custom profile header banner illustrating Stack and Positioned coordinate layouts.
    class ProfileHeaderBanner extends StatelessWidget {
      final String coverImageUrl;
      final String avatarImageUrl;
      final String userName;
      final bool isOnline;
    
      const ProfileHeaderBanner({
        super.key,
        required this.coverImageUrl,
        required this.avatarImageUrl,
        required this.userName,
        required this.isOnline,
      });
    
      @override
      Widget build(BuildContext context) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // Container wrapping our Stack layout
            SizedBox(
              height: 220,
              child: Stack(
                clipBehavior: Clip.none, // Allows avatar avatar to overflow the stack boundaries
                children: [
                  // Layer 1: Background Cover Photo (Non-positioned child sizing the width)
                  Positioned.fill(
                    child: ClipRRect(
                      borderRadius: const BorderRadius.vertical(
                        top: Radius.circular(16.0),
                      ),
                      child: Image.network(
                        coverImageUrl,
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
                  
                  // Layer 2: Visual Gradient overlay on top of cover image
                  Positioned.fill(
                    child: DecoratedBox(
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          begin: Alignment.topCenter,
                          end: Alignment.bottomCenter,
                          colors: [
                            Colors.black.withOpacity(0.1),
                            Colors.black.withOpacity(0.5),
                          ],
                        ),
                      ),
                    ),
                  ),
                  
                  // Layer 3: Overlapping Profile Avatar placing at bottom-center boundary
                  Positioned(
                    bottom: -40, // Offsets the avatar image halfway outside the container boundary
                    left: 0,
                    right: 0,
                    child: Center(
                      child: Stack(
                        clipBehavior: Clip.none,
                        children: [
                          // Avatar border casing
                          Container(
                            decoration: BoxDecoration(
                              shape: BoxShape.circle,
                              border: Border.all(color: Colors.white, width: 4.0),
                              boxShadow: [
                                BoxShadow(
                                  color: Colors.black.withOpacity(0.15),
                                  blurRadius: 8,
                                  offset: const Offset(0, 4),
                                ),
                              ],
                            ),
                            child: CircleAvatar(
                              radius: 50,
                              backgroundImage: NetworkImage(avatarImageUrl),
                              backgroundColor: Colors.grey[200],
                            ),
                          ),
                          
                          // Layer 4: Online Status Badge Indicator inside avatar stack
                          Positioned(
                            bottom: 4,
                            right: 4,
                            child: Container(
                              height: 18,
                              width: 18,
                              decoration: BoxDecoration(
                                color: isOnline ? Colors.green : Colors.grey,
                                shape: BoxShape.circle,
                                border: Border.all(color: Colors.white, width: 2.5),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ),
            
            // Dynamic spacer representing offset placeholder margin
            const SizedBox(height: 52.0),
            
            // Profile username title below banner stack
            Text(
              userName,
              style: const TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
                letterSpacing: 0.5,
              ),
            ),
          ],
        );
      }
    }
    

    Code Explanation:

    1. clipBehavior: Clip.none: By default, Stacks clip any children that overflow their boundaries. Setting Clip.none allows the profile avatar to hang halfway outside the cover image.
    2. Positioned.fill: A helper constructor that expands the cover image and the black gradient decoration to fill the entire width and height of the stack.
    3. bottom: -40: Vertically offsets the avatar container down past the bottom edge of the cover background.
    4. Nested Stacks: The status badge is placed inside a nested Stack wrapping the CircleAvatar to ensure the status indicator aligns precisely with the avatar's bottom-right quadrant.

    Common Mistakes

    1. Using Positioned Outside a Stack

    Attempting to wrap a widget in Positioned inside standard layout flow components (like a Row, Column, or padding container) will trigger a compiler or layout build crash.

    • The Fix: Ensure that Positioned widgets are immediate children of a Stack widget.

    2. Sizing Collapses (Unconstrained Stacks)

    If a stack container has no non-positioned children and has no parent layout constraints defining its dimensions, it will collapse to a width/height of zero.

    • The Fix: Define strict dimensions on the parent container (like wrapping the stack in a SizedBox) or make sure the stack contains at least one non-positioned widget that has clear dimensions.

    3. Tap Interception Issues

    Because widgets are layered on top of each other, invisible transparent layers can intercept tap events, preventing users from clicking buttons underneath them.

    • The Fix: Use IgnorePointer or AbsorbPointer on decorative/transparent overlay widgets to let clicks pass through to interactive buttons below.

    Best Practices

    • Explicitly Configure Clip Actions: Set clipBehavior: Clip.none when building overflow configurations like user cards or sliding drawers to prevent layout clipping.
    • Use Positioned.fill for Backgrounds: Avoid manual coordinate properties (top: 0, left: 0) for backgrounds. Use Positioned.fill to automatically expand background decorations.
    • Favor Composition Over Absolute Stacks: Do not rely on Stacks for linear layout alignment. Stacks are expensive to compile and compute compared to rows and columns; only use them when overlapping UI components is necessary.

    Related Concepts

    Continue master layout mechanics by exploring these guides:


    Summary

    • Stack layers widgets sequentially from bottom to top based on their declaration order.
    • Positioned widgets allow you to set absolute distances (top, bottom, left, right) from the boundaries of the parent Stack.
    • Configure clipBehavior: Clip.none on a Stack to allow elements to overflow its boundaries.
    • Stacks determine their sizes from the dimensions of their non-positioned child widgets.

    Next Steps

    1. Previous Guide: Row vs. Column Layout Guide
    2. Next Guide: Building Responsive Layouts
    3. Related Guide: Stack and Positioned Widgets

    Ready to take the next step?

    Start our free Flutter course and build real apps today.

    Start Learning Now