Stack & Positioned
Introduction to Stack
While Row and Column arrange children linearly, the Stack widget allows you to overlap children on top of each other. Think of it as layering widgets like cards on a table -- the first child is at the bottom, and each subsequent child is painted on top.
Stack is essential for building UIs that require overlapping elements: badges on icons, text overlays on images, floating action buttons, and complex card layouts.
Basic Stack Usage
A Stack sizes itself to contain all of its non-positioned children and then places each child at the top-left corner by default.
Simple Stack Example
Stack(
children: <Widget>[
Container(
width: 200,
height: 200,
color: Colors.blue,
),
Container(
width: 150,
height: 150,
color: Colors.red,
),
Container(
width: 100,
height: 100,
color: Colors.yellow,
),
],
)
// Three overlapping squares: blue (back), red (middle), yellow (front)
All three containers start at the top-left corner of the Stack, creating a layered effect. The Stack’s size is determined by the largest non-positioned child (200x200 in this case).
The Positioned Widget
Positioned is used inside a Stack to control exactly where a child is placed. You specify distances from the Stack’s edges using top, bottom, left, and right properties.
Positioned Widget Example
Stack(
children: <Widget>[
Container(
width: 300,
height: 200,
color: Colors.grey[300],
),
Positioned(
top: 10,
left: 10,
child: Container(
width: 80,
height: 80,
color: Colors.blue,
child: Center(child: Text('TL', style: TextStyle(color: Colors.white))),
),
),
Positioned(
bottom: 10,
right: 10,
child: Container(
width: 80,
height: 80,
color: Colors.red,
child: Center(child: Text('BR', style: TextStyle(color: Colors.white))),
),
),
],
)
The blue container is positioned 10px from the top and left edges. The red container is positioned 10px from the bottom and right edges. The grey container is non-positioned and determines the Stack’s size.
left and right) to stretch a Positioned child across the Stack. The child’s size is then determined by the distance between the edges.Positioned.fill
A common need is making a Positioned child fill the entire Stack. Positioned.fill is a convenience constructor that sets all four edges to 0:
Positioned.fill Example
Stack(
children: <Widget>[
// Background image fills the entire Stack
Positioned.fill(
child: Image.network(
'https://example.com/photo.jpg',
fit: BoxFit.cover,
),
),
// Dark overlay
Positioned.fill(
child: Container(color: Colors.black.withOpacity(0.4)),
),
// Text on top
Center(
child: Text(
'Welcome',
style: TextStyle(color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold),
),
),
],
)
Alignment in Stack
The alignment property controls where non-positioned children are placed within the Stack. By default, it is AlignmentDirectional.topStart.
Stack Alignment
// Center all non-positioned children
Stack(
alignment: Alignment.center,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.blue),
Container(width: 100, height: 100, color: Colors.red),
// Red is centered on blue
],
)
// Bottom-right alignment
Stack(
alignment: Alignment.bottomRight,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.blue),
Container(width: 100, height: 100, color: Colors.red),
// Red is at bottom-right of blue
],
)
alignment property only affects non-positioned children. Children wrapped in Positioned are placed according to their explicit top/bottom/left/right values and ignore the Stack’s alignment.The fit Property
The fit property controls how non-positioned children are sized relative to the Stack:
- StackFit.loose (default) -- Non-positioned children can be any size up to the Stack’s size. The Stack imposes maximum constraints from its parent but allows children to be smaller.
- StackFit.expand -- Non-positioned children are forced to fill the entire Stack. The constraints are set to be exactly the Stack’s size.
- StackFit.passthrough -- The constraints from the Stack’s parent are passed directly to non-positioned children without modification.
StackFit Examples
// Children can be any size (default)
SizedBox(
width: 300,
height: 200,
child: Stack(
fit: StackFit.loose,
children: [
Container(width: 100, height: 100, color: Colors.blue),
// Blue is 100x100 within a 300x200 Stack
],
),
)
// Children are forced to fill the Stack
SizedBox(
width: 300,
height: 200,
child: Stack(
fit: StackFit.expand,
children: [
Container(color: Colors.blue),
// Blue fills the entire 300x200 Stack
],
),
)
clipBehavior
The clipBehavior property controls what happens when Positioned children extend beyond the Stack’s boundaries:
- Clip.hardEdge (default) -- Children that overflow the Stack’s bounds are clipped.
- Clip.none -- Children can visually extend beyond the Stack’s bounds. Useful for shadows, tooltips, or decorative elements.
- Clip.antiAlias -- Clipped with anti-aliasing for smoother edges.
clipBehavior Example
Stack(
clipBehavior: Clip.none,
children: <Widget>[
Container(width: 200, height: 200, color: Colors.blue),
Positioned(
top: -20,
right: -20,
child: Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Center(child: Text('3', style: TextStyle(color: Colors.white))),
),
),
],
)
// Red circle extends 20px beyond top-right corner of the Stack
Clip.none, overflowing children are still painted but do not receive hit-test events outside the Stack’s bounds. This means users cannot tap on the overflowing portion of a button or interactive widget.IndexedStack
IndexedStack is a variant of Stack that only displays one child at a time based on an index. All children are laid out and maintain their state, but only the child at the specified index is visible.
IndexedStack Example
class TabView extends StatefulWidget {
@override
State<TabView> createState() => _TabViewState();
}
class _TabViewState extends State<TabView> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: IndexedStack(
index: _selectedIndex,
children: <Widget>[
Center(child: Text('Home Page', style: TextStyle(fontSize: 24))),
Center(child: Text('Search Page', style: TextStyle(fontSize: 24))),
Center(child: Text('Profile Page', style: TextStyle(fontSize: 24))),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(icon: Icon(Icons.home), onPressed: () => setState(() => _selectedIndex = 0)),
IconButton(icon: Icon(Icons.search), onPressed: () => setState(() => _selectedIndex = 1)),
IconButton(icon: Icon(Icons.person), onPressed: () => setState(() => _selectedIndex = 2)),
],
),
],
);
}
}
Practical Example: Badge on Icon
A notification badge on an icon is one of the most common Stack patterns:
Notification Badge
Stack(
clipBehavior: Clip.none,
children: <Widget>[
Icon(Icons.notifications, size: 32, color: Colors.grey[700]),
Positioned(
top: -5,
right: -5,
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
constraints: BoxConstraints(minWidth: 20, minHeight: 20),
child: Text(
'5',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
),
],
)
Practical Example: Overlay Text on Image
A hero image with text overlay, commonly used in cards and banners:
Image with Text Overlay
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Stack(
children: <Widget>[
// Background image
Image.network(
'https://example.com/city.jpg',
width: double.infinity,
height: 200,
fit: BoxFit.cover,
),
// Gradient overlay
Positioned.fill(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.transparent, Colors.black.withOpacity(0.7)],
),
),
),
),
// Text content
Positioned(
bottom: 16,
left: 16,
right: 16,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('New York City', style: TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)),
SizedBox(height: 4),
Text('Explore the city that never sleeps', style: TextStyle(color: Colors.white70, fontSize: 14)),
],
),
),
],
),
)
Practical Example: Floating Labels
A text field with a custom floating label positioned outside the field boundary:
Custom Floating Label
Stack(
clipBehavior: Clip.none,
children: <Widget>[
Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 16),
decoration: BoxDecoration(
border: Border.all(color: Colors.blue, width: 2),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.email, color: Colors.blue),
SizedBox(width: 8),
Expanded(
child: Text('user@example.com', style: TextStyle(fontSize: 16)),
),
],
),
),
Positioned(
top: -10,
left: 12,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8),
color: Colors.white,
child: Text('Email', style: TextStyle(color: Colors.blue, fontSize: 12)),
),
),
],
)
Practical Example: Custom Card with Overlay
A product card with a discount badge, favorite button, and overlay content:
Product Card with Overlays
Card(
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Stack(
children: <Widget>[
// Product image
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
'https://example.com/product.jpg',
width: double.infinity,
height: 180,
fit: BoxFit.cover,
),
Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Premium Headphones', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(height: 4),
Text('\$99.99', style: TextStyle(fontSize: 18, color: Colors.green, fontWeight: FontWeight.bold)),
],
),
),
],
),
// Discount badge (top-left)
Positioned(
top: 8,
left: 8,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(4),
),
child: Text('-30%', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12)),
),
),
// Favorite button (top-right)
Positioned(
top: 8,
right: 8,
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 18,
child: Icon(Icons.favorite_border, color: Colors.red, size: 20),
),
),
],
),
)
Summary
- Stack overlaps children on top of each other -- first child at the back, last at the front.
- Positioned places a child at exact coordinates within the Stack using top, bottom, left, and right.
Positioned.fillstretches a child to cover the entire Stack.- The
alignmentproperty controls where non-positioned children are placed. - StackFit controls sizing:
loose(children choose size),expand(children fill Stack),passthrough(parent constraints passed through). clipBehaviorcontrols overflow:Clip.noneallows visible overflow but blocks hit-testing outside bounds.- IndexedStack shows only one child at a time while preserving all children’s state.
- Common patterns: badges, image overlays, floating labels, and complex card layouts.