Composing Widgets Together
How small, single-purpose widgets nest inside each other to build up real screens.
2 min read
The previous lesson introduced the idea that everything in Flutter is a widget. This lesson focuses on the skill you'll use constantly: composing many small widgets into one screen, instead of reaching for a single widget that does everything.
Composition over configuration
Flutter widgets are deliberately narrow. Padding only adds space. Center only centers. Text only displays a string. There's no Text widget with a padding property or a centered boolean — you get that behavior by wrapping widgets around each other.
Padding(
padding: const EdgeInsets.all(16.0),
child: Center(
child: Text(
'Welcome back',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
),
)This might feel verbose coming from frameworks where a single component takes a dozen props. But narrow widgets are easier to reason about individually, easier to reuse in different combinations, and easier for Flutter to optimize — it only needs to know how to rebuild each small piece, not every possible combination of features.
The child and children properties
Most layout widgets accept either a single child (one nested widget) or a list of children (multiple nested widgets), which is how trees form:
Column(
children: [
const Text('Item one'),
const Text('Item two'),
ElevatedButton(
onPressed: () {},
child: const Text('Tap me'),
),
],
)Notice ElevatedButton itself takes a child — the button doesn't know how to display "Tap me" as text on its own; it delegates that to a nested Text widget. This pattern repeats everywhere: a widget provides behavior or layout, and you supply content as a child.
Extracting your own widgets
Once a chunk of the tree gets reused, or just gets deep enough to hurt readability, pull it into its own widget class rather than a helper function. This keeps Flutter's rebuild optimizations working correctly and keeps your build methods short:
class GreetingCard extends StatelessWidget {
final String name;
const GreetingCard({super.key, required this.name});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Hello, $name!', style: const TextStyle(fontSize: 20)),
);
}
}Now GreetingCard(name: 'Alice') can be used anywhere, just like a built-in widget — Flutter draws no distinction between widgets you write and widgets that ship with the framework.
Why this matters
Real Flutter screens are trees dozens of widgets deep, but you never write that whole tree in one place. You build small pieces (a card, a list item, a form field), and compose them into larger pieces, the same way you compose functions in ordinary Dart code. Getting comfortable reading a nested tree of child:/children: properties is one of the fastest ways to get productive in Flutter, and it's what every lesson from here on builds on.