Widgets and layout
Stateless versus stateful widgets, how the layout constraints propagate, and the container widgets that do most of the work.
Stateless and stateful
A StatelessWidget is immutable: given the same inputs it always builds the same output. A StatefulWidget pairs an immutable widget with a long-lived State object that can call setState and rebuild.
| Widget | Holds mutable state | Typical use |
|---|---|---|
StatelessWidget | No | Labels, icons, rows, purely derived UI |
StatefulWidget | Yes | Forms, animations, local counters, anything with a controller |
InheritedWidget | No | Exposing data to a whole subtree (the basis of theming) |
class Tag extends StatelessWidget {
const Tag({super.key, required this.label});
final String label;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.circular(6),
),
child: Text(label, style: Theme.of(context).textTheme.labelMedium),
);
}
}Constraints flow down, sizes flow up
Each widget receives constraints from its parent and returns a size to it, then the parent positions the child. Most layout confusion disappears once you know which constraint your child was actually given.
Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Padding(
padding: EdgeInsets.all(16),
child: Text('Recent notes', style: TextStyle(fontSize: 20)),
),
Expanded(
child: ListView.builder(
itemCount: notes.length,
itemBuilder: (context, index) => ListTile(
title: Text(notes[index].title),
trailing: const Icon(Icons.chevron_right),
),
),
),
SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.all(16),
child: FilledButton(
onPressed: addNote,
child: const Text('New note'),
),
),
),
],
),
)ColumnandRoware unbounded along their main axis, so a long child needsExpanded,Flexibleor a scrollable such asListView.Stackoverlays children; usePositionedwhen you need exact placement, andAlignfor corners.SafeAreakeeps content clear of notches and gesture bars.MediaQueryexposes the real window size and text scale.- Check the app at a large text scale and in landscape before calling a screen finished — overflow stripes are the usual result.
⚠️
A red overflow stripe means a child wanted more space than its parent allowed. Do not paper over it with a smaller font: give the flexible child an
Expanded, make the parent scrollable, or wrap the text with Flexible and allow it to wrap.FAQ
Why does my Column overflow but my ListView does not?
A scrollable gives its children unbounded main-axis space, so nothing overflows; it just scrolls. A Column gives its children the remaining fixed space, and it will not clip or scroll on its own.
Should every widget be its own class?
Extract a widget when it is reused, when it has state, or when the build method becomes hard to read. Small private helper methods are fine for a single call site.
Related
Setup and your first app State, navigation and release builds
Last refreshed 2026-09-18.