Flutter has no CSS. Layouts are built entirely from widgets. Master these layout widgets and you can build any UI.
Flutter's layout system is based on constraints flowing down and sizes flowing up. A parent passes constraints (min/max width and height) to its children, the children decide their size within those constraints, and the parent positions them.
| Category | Widgets | Description |
|---|---|---|
| Single-child | Container, Center, Padding, Align, SizedBox, FittedBox, AspectRatio | Wraps one child widget |
| Multi-child | Column, Row, Stack, Wrap, Flow | Arranges multiple children |
| Scrollable | ListView, GridView, SingleChildScrollView, CustomScrollView | Children can scroll |
| Flex helpers | Expanded, Flexible, Spacer | Fill remaining space in Row/Column |
Column arranges children vertically (top to bottom). Its main axis is vertical; cross axis is horizontal.
Column(
// mainAxisAlignment — vertical spacing of children
mainAxisAlignment: MainAxisAlignment.center,
// Options: start | end | center | spaceBetween | spaceAround | spaceEvenly
// crossAxisAlignment — horizontal alignment of children
crossAxisAlignment: CrossAxisAlignment.stretch,
// Options: start | end | center | stretch | baseline
// How much vertical space the Column occupies
mainAxisSize: MainAxisSize.min, // shrink-wrap (default: max = fill all height)
children: [
const Text('First item'),
const SizedBox(height: 16), // spacing
const Text('Second item'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () {},
child: const Text('Button'),
),
],
)
// MainAxisAlignment options visualised:
// start [A B C ]
// end [ A B C]
// center [ A B C ]
// spaceBetween[A B C ]
// spaceAround [ A B C ]
// spaceEvenly [ A B C ]
Row arranges children horizontally (left to right). Works exactly like Column but rotated: main axis is horizontal, cross axis is vertical.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Leading icon
const Icon(Icons.menu, size: 28),
// Title (middle)
const Text('My App', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
// Actions (trailing)
Row(
children: [
IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
],
),
],
)
// Row overflow — if children are too wide, use Flexible or Expanded
// or wrap in SingleChildScrollView with scrollDirection: Axis.horizontal
Row(
children: [
Expanded(child: Text('Long text that takes remaining space')),
const SizedBox(width: 8),
const Icon(Icons.chevron_right),
],
)
Flexible or Expanded, or place the Row inside a SingleChildScrollView(scrollDirection: Axis.horizontal).Stack overlays children on top of each other (like CSS position: relative). Use Positioned to place children at specific offsets within the Stack.
Stack(
// How to size non-positioned children
fit: StackFit.loose, // children choose own size (default)
// fit: StackFit.expand, // children fill the stack
// Alignment of non-positioned children
alignment: Alignment.center,
children: [
// Bottom layer — full background image
Image.network(
'https://picsum.photos/400/300',
width: 400,
height: 300,
fit: BoxFit.cover,
),
// Middle layer — semi-transparent overlay
Container(
width: 400,
height: 300,
color: Colors.black45,
),
// Top layer — positioned text at bottom-left
const Positioned(
left: 16,
bottom: 16,
child: Text(
'Caption Text',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
// Badge at top-right
const Positioned(
right: 8,
top: 8,
child: CircleAvatar(
radius: 12,
backgroundColor: Colors.red,
child: Text('3', style: TextStyle(color: Colors.white, fontSize: 11)),
),
),
],
)
// Positioned parameters
Positioned(
left: 10, // from left edge
top: 10, // from top edge
right: 10, // from right edge (or use width)
bottom: 10, // from bottom edge (or use height)
width: 100, // explicit width (optional)
height: 50, // explicit height (optional)
child: myWidget,
)
Expanded and Flexible are used inside Row or Column to distribute remaining space among children.
// Expanded — child fills ALL remaining space along the main axis
Row(
children: [
const Icon(Icons.person),
const SizedBox(width: 8),
Expanded(
child: Text('This text fills all remaining horizontal space'),
),
const Icon(Icons.chevron_right),
],
)
// Multiple Expanded with flex ratios
Row(
children: [
Expanded(flex: 2, child: Container(color: Colors.blue)), // 2/3
Expanded(flex: 1, child: Container(color: Colors.red)), // 1/3
],
)
// Flexible — similar but child CAN be smaller than its share
// Use FlexFit.tight (same as Expanded) or FlexFit.loose (can be smaller)
Flexible(
flex: 1,
fit: FlexFit.loose, // child can be smaller than available space
child: Text('Short'),
)
// Column example with Expanded
Column(
children: [
const Text('Header', style: TextStyle(fontSize: 24)),
Expanded(
// This widget fills all remaining vertical space
child: ListView(
children: List.generate(20, (i) => ListTile(title: Text('Item $i'))),
),
),
ElevatedButton(onPressed: () {}, child: const Text('Footer Button')),
],
)
// SizedBox — fixed-size box or spacing gap
SizedBox(width: 16) // horizontal gap in Row
SizedBox(height: 16) // vertical gap in Column
SizedBox(width: 200, height: 100, child: myWidget) // constrain child size
SizedBox.expand(child: myWidget) // fill all available space
SizedBox.shrink() // takes zero space (useful as placeholder)
// Spacer — flexible empty space inside Row/Column (like a spring)
Row(
children: [
const Text('Left'),
const Spacer(), // pushes items apart
const Text('Right'),
],
)
// Spacer with flex
Row(
children: [
const Text('Left'),
const Spacer(flex: 2),
const Text('Center'),
const Spacer(flex: 1),
const Text('Right'),
],
)
// Padding — add space around a child
Padding(
padding: const EdgeInsets.all(16),
child: const Text('Padded text'),
)
// Center — centers child in available space
const Center(
child: Text('I am centered'),
)
// Align — position child at specific alignment within parent
Align(
alignment: Alignment.topRight, // or Alignment(1.0, -1.0)
child: const Text('Top right'),
)
// Common Alignment values:
// Alignment.topLeft Alignment.topCenter Alignment.topRight
// Alignment.centerLeft Alignment.center Alignment.centerRight
// Alignment.bottomLeft Alignment.bottomCenter Alignment.bottomRight
Scaffold is the top-level visual structure of a screen. It provides slots for AppBar, body, drawer, bottom navigation, and FAB.
Scaffold(
// Top app bar
appBar: AppBar(
title: const Text('Home'),
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
IconButton(onPressed: () {}, icon: const Icon(Icons.more_vert)),
],
),
// Main content area (fills space between appBar and bottom)
body: const Center(child: Text('Body content')),
// Floating action button
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
// FAB position
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
// Bottom navigation bar
bottomNavigationBar: BottomNavigationBar(
currentIndex: 0,
onTap: (index) {},
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
),
// Side drawer
drawer: const Drawer(child: Text('Drawer content')),
// Right-side drawer
endDrawer: const Drawer(child: Text('End drawer')),
// Background color of the body area
backgroundColor: Colors.grey.shade100,
// Show/hide resize when keyboard appears
resizeToAvoidBottomInset: true,
)
AppBar(
// Left side — auto-shows back arrow or hamburger menu if drawer exists
leading: IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(Icons.arrow_back),
),
automaticallyImplyLeading: false, // suppress auto back arrow
// Center title
title: const Text('My Screen'),
centerTitle: true, // center on both Android and iOS
// Right-side action buttons
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
IconButton(onPressed: () {}, icon: const Icon(Icons.notifications)),
PopupMenuButton<String>(
onSelected: (value) {},
itemBuilder: (context) => [
const PopupMenuItem(value: 'settings', child: Text('Settings')),
const PopupMenuItem(value: 'logout', child: Text('Logout')),
],
),
],
// Styling
backgroundColor: Colors.blue.shade800,
foregroundColor: Colors.white, // color of title and icons
elevation: 4, // shadow depth
shadowColor: Colors.black38,
// Widget shown below the AppBar (e.g., TabBar)
bottom: const TabBar(tabs: [Tab(text: 'All'), Tab(text: 'Active')]),
)
// Simple ListView (small lists only — all items built at once)
ListView(
padding: const EdgeInsets.all(8),
children: [
ListTile(
leading: const Icon(Icons.inbox),
title: const Text('Inbox'),
subtitle: const Text('5 new messages'),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
const Divider(),
ListTile(
leading: const CircleAvatar(child: Text('A')),
title: const Text('Alice'),
onTap: () {},
),
],
)
// ListView.builder — efficient for long/infinite lists
// Only builds items that are visible on screen
ListView.builder(
itemCount: items.length,
itemExtent: 72, // fixed height per item (optional, more efficient)
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
key: ValueKey(item.id),
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(item.title),
subtitle: Text(item.subtitle),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteItem(index),
),
);
},
)
// ListView.separated — adds separator between items
ListView.separated(
itemCount: items.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)
// GridView with fixed number of columns
GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 2 columns
crossAxisSpacing: 8, // horizontal gap
mainAxisSpacing: 8, // vertical gap
childAspectRatio: 1.0, // width / height ratio (1.0 = square)
),
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return Card(
child: Column(
children: [
Expanded(
child: Image.network(product.imageUrl, fit: BoxFit.cover),
),
Padding(
padding: const EdgeInsets.all(8),
child: Text(product.name, textAlign: TextAlign.center),
),
],
),
);
},
)
// GridView with max item extent (auto-calculates columns based on screen width)
GridView.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200, // each item max 200px wide
mainAxisExtent: 200, // each item 200px tall
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: 20,
itemBuilder: (context, index) => Card(
child: Center(child: Text('Item $index')),
),
)
ListView.builder or GridView.builder for lists with more than ~20 items. The builder variants only construct visible items, keeping memory usage low.