skip to content

In Flutter, how does a Stack decide its own size and place its children, and how do positioned and non-positioned children differ?

level: juniorimportance: must knowfreq 70%

answer

  1. largest non-positioned child
  2. alignment defaults to topStart
  3. Positioned is parent data
  4. left plus right makes width tight
  5. later children paint on top

basics

~20 s

A Stack sizes itself to its largest non-positioned child, within its constraints, and aligns those children (default top-start). Positioned children don't affect its size; they are pinned by their insets afterwards, and later children paint on top.

solid answer

~40 s

A `Stack` lays out its **non-positioned** children first. With the default `fit: StackFit.loose` they get the Stack's constraints loosened; the Stack becomes as big as the largest of them and aligns them with `alignment`, `AlignmentDirectional.topStart` by default. **Positioned** children, wrapped in `Positioned`, are laid out afterwards against that size. Setting `left` and `right`, or `width`, gives a tight width; with neither, the width is unconstrained. On an axis with no inset the child is placed by the Stack's alignment. Paint order is list order: later children are drawn over earlier ones. If every child is positioned, the Stack takes the biggest size its constraints allow, so in an unbounded parent it asserts. For an avatar, the `CircleAvatar` is the non-positioned child that sets the size, and the status badge is `Positioned(right: 0, bottom: 0)`.

code

dart · 31 lines
dart
import 'package:flutter/material.dart';

class ProfileAvatar extends StatelessWidget {
  const ProfileAvatar({super.key, required this.image, required this.online});

  final ImageProvider image;
  final bool online;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        CircleAvatar(radius: 36, backgroundImage: image), // sets the 72 x 72 size
        if (online)
          Positioned(
            right: 0,
            bottom: 0,
            child: Container(
              width: 14,
              height: 14,
              decoration: BoxDecoration(
                color: Colors.green,
                shape: BoxShape.circle,
                border: Border.all(color: Colors.white, width: 2),
              ),
            ),
          ),
      ],
    );
  }
}

go deeper

for a junior

Recall that non-positioned children set the Stack's size, Positioned pins children to edges and the last child paints on top.

for a middle

Explain the sizing algorithm, how insets turn into tight or unconstrained constraints, and how alignment places partially positioned children.

for a senior

Predict and fix Stack failures in unbounded parents and positioned text that won't wrap, and structure overlapping UI so size-defining content is explicit.

for a principal

Set conventions for overlay-style components, such as which child owns the size and where badges live, so shared widgets behave predictably inside lists and grids.

## What a Stack is for `Stack` paints its children on top of each other in the same rectangle. It is the tool for overlapping content inside one widget: a badge on an avatar, a gradient over an image, a floating button in a corner. Content that must float above the whole route belongs to the `Overlay` instead. The key to predicting a `Stack` is that it treats two kinds of children differently. | | Non-positioned child | Positioned child | |---|---|---| | How you write it | any widget | wrapped in `Positioned` (or `Positioned.fill`, `Positioned.fromRect`, `Positioned.directional`) | | When laid out | first | after the Stack knows its size | | Constraints | the Stack's constraints transformed by `fit` | derived from its insets and `width`/`height` | | Affects the Stack's size | yes | no | | Placement | by the Stack's `alignment` | by its insets; by `alignment` on an axis without insets | ## Sizing: non-positioned children decide The algorithm in `RenderStack`: 1. Lay out every non-positioned child with the constraints chosen by `fit`. The default, `StackFit.loose`, loosens the Stack's constraints, so each child can be anything from zero up to the maximum. 2. The Stack's size is the largest width and the largest height among those children, within its own constraints. 3. If there are **no** non-positioned children, the Stack takes the biggest size its constraints allow. In an unbounded parent, for example inside a `Column` along the height, that is infinite, and debug mode asserts with 'A Stack requires bounded constraints from its parent.' 4. Lay out and place the positioned children against the final size. This is why an avatar stack is exactly the avatar's size: the `CircleAvatar` is the only non-positioned child. ## Positioned: pinning to edges `Positioned` is not a layout widget; it is a `ParentDataWidget` that stores insets on the child for the Stack to read, so it must be a direct child of a `Stack`. Its fields are `left`, `top`, `right`, `bottom`, `width` and `height`, and at most two of the three horizontal values (and two of the three vertical ones) may be set. The child's constraints come from those values: - `left` **and** `right` set: the width is tight, equal to the Stack width minus both insets. - `width` set: the width is tight at that value. - Neither: the width is **unconstrained**, zero to infinity. A long `Text` in `Positioned(top: 0, left: 0)` will not wrap; it runs past the Stack. The same rules apply vertically. `Positioned.fill()` sets all four insets to `0.0`, so the child exactly covers the Stack, which is handy for a scrim or an ink layer. Placement follows the insets: `left` places the left edge, or `right` places the right edge from the Stack's right side. A child with neither `left` nor `right` is a **partially positioned** child, and on that axis it is placed by the Stack's `alignment`, just like a non-positioned child. ## Paint order and the avatar Children paint in list order, so the last child is on top. For the profile avatar: - `CircleAvatar(radius: 36)` first: it sets the 72 by 72 size. - The online-status dot next: `Positioned(right: 0, bottom: 0, child: ...)`, a 14-pixel circle with a white ring. - The camera button last, so it is drawn above the dot if they overlap. ## Common mistakes - Expecting a positioned child to grow the Stack: it never does. - Wrapping `Positioned` in `Padding` or `Center`: it must be the Stack's direct child. - Relying on a Stack with only positioned children inside a scrollable or a `Column`: give it a size with `SizedBox`. - Forgetting that the default `alignment` is directional (`topStart`), which needs an ambient `Directionality`, normally provided by `MaterialApp` or `WidgetsApp`.

  • In Flutter, what happens if every child of a Stack is wrapped in Positioned?
    No child contributes to the size, so the Stack takes the biggest size its constraints allow. On a screen that is fine, but in a parent with an unbounded axis, such as inside a `Column` or a scroll view, the size would be infinite and debug mode asserts that a Stack requires bounded constraints. Give it an explicit size with `SizedBox` or add a non-positioned child.
  • Why does a long Text inside Positioned(top: 8, left: 8) not wrap in Flutter?
    With only `left` set there is no `right` or `width`, so the positioned child's width is unconstrained: zero to infinity. The `Text` lays out on one line at its natural width and can run past the Stack. Add `right: 8` (or a `width`) to give it a tight width it can wrap within.

saying these in an interview costs you the question

  • A Positioned child makes the Stack grow to contain it.
  • Stack children are painted with the first child on top.
  • Positioned can be nested inside Padding within a Stack.
  • A Stack with only positioned children sizes itself to them.
  • Stack's default alignment is center.