A Stack is a widget that places elements on top of each other, like layers.
So far, we’ve been very orderly. We’ve placed things side by side (Row) or one below the other (Column). But modern design isn’t always a perfect grid.
Sometimes we need to put text on top of an image. Or a red notification circle on top of an icon. Or a floating button in a corner.
To work on this “Z-axis” (depth), Flutter offers us the Stack.
The Concept of Layers
The Stack works just like layers in Photoshop or a stack of papers on your desk.
- You put down the first sheet (the background).
- You put the second sheet on top (it covers the previous one).
- And so on.
In code, this means that the order in the children list matters a lot.
- The first widget in the list is the one at the bottom (Base).
- The last widget in the list is the one on top of everything (Top).
Stack(
children: [
// 1. Background (Green)
Container(width: 300, height: 300, color: Colors.green),
// 2. Middle layer (Red)
Container(width: 200, height: 200, color: Colors.red),
// 3. Topmost layer (Blue)
Container(width: 100, height: 100, color: Colors.blue),
],
)On the Web we use z-index to control who covers whom. In Flutter there is no z-index. The paint order is defined exclusively by the order in the list.
General Alignment
By default, the Stack places everything in the top-left corner (topStart). If we want all elements to be centered or go to a corner, we use the alignment property.
Stack(
alignment: Alignment.center, // All children are centered
children: [
Container(width: 300, height: 300, color: Colors.green),
Text("Hello World"), // Will appear centered over the green square
],
)Positioned: Position Control
Alignment is fine for simple things, but what if I want the text at the bottom right and the icon at the top left?
For that, we wrap the child in a Positioned widget.
This widget only works if it is a direct descendant of a Stack. It allows us to set the position using distances: top, bottom, left, right.
Stack(
children: [
// Background: An image
Container(color: Colors.blue, width: 300, height: 300),
// Element 1: Text at the bottom (10px from the bottom edge)
Positioned(
bottom: 10,
left: 10,
child: Text("Image Title", style: TextStyle(color: Colors.white)),
),
// Element 2: Icon at the top right
Positioned(
top: 10,
right: 10,
child: Icon(Icons.favorite, color: Colors.white),
),
],
)If you use Positioned, the child widget can change its size. If you set left: 0 and right: 0, you are forcing the child to stretch across the entire width of the Stack.
Practical Example: Notification Badge
A typical use case is the icon that indicates you have unread messages. Let’s build it:
- Base: The bell icon.
- Top layer: A small red circle positioned in the corner.
Stack(
clipBehavior: Clip.none, // Allows the red dot to slightly overflow if needed
children: [
// 1. The base Icon
Icon(Icons.notifications, size: 50, color: Colors.grey),
// 2. The red dot
Positioned(
right: 0,
top: 0,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'3',
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
)
],
)How big is a Stack?
The Stack tries to adjust its size to its non-positioned children.
- If it has a background image of 300x300 (non-positioned), the Stack will measure 300x300.
- If all children are positioned, the
Stacktries to occupy all the space allowed by its parent’s constraints. If it receives unbounded constraints, you will need to provide a size for it.
Rule of thumb: A non-positioned background child is usually the easiest way to define the size of your canvas.
Positioned.fill
A very useful shortcut. If you want an element to occupy all the space of the Stack (e.g., a background image or a semi-transparent overlay), use Positioned.fill.
Stack(
children: [
Positioned.fill(
child: Image.network("...", fit: BoxFit.cover),
),
Center(child: Text("Text over image")),
],
)This is equivalent to setting top: 0, bottom: 0, left: 0, right: 0.