Flutter takes inspiration from React in its widget model — the UI is a function of the current data state. When state changes, Flutter efficiently rebuilds only the affected widgets. Mastering widgets means mastering Flutter itself.
What Is a Widget?
A widget is an immutable description of part of the user interface. Widgets form a hierarchical widget tree, where parent widgets contain child widgets. Flutter traverses this tree to determine what to render on screen.
Widget Tree Example
-------------------
MaterialApp
└── Scaffold
├── AppBar
│ └── Text ("My App")
└── Body
└── Column
├── Text ("Hello")
├── Icon (Icons.star)
└── ElevatedButton
└── Text ("Press Me")
Widgets serve two purposes:
- Display data — render text, images, icons, and custom shapes
- Capture user input — respond to taps, swipes, text entry, and gestures
Widget Types
Widgets
├── Stateless (immutable, no internal state)
│ Examples: Text, Icon, Image
└── Stateful (maintains mutable state over time)
Examples: Checkbox, Slider, TextField
Stateless widgets are rebuilt only when their parent rebuilds them, making them efficient for static content. Stateful widgets track their own mutable data and can rebuild themselves independently when that data changes.
Basic Widgets
Basic widgets build the core visible content of an app.
Text
The most fundamental display widget. Renders a string with optional styling.
Text(
'Hello, Flutter!',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
)
Image
Loads images from the network, assets, or files.
// From network URL
Image.network('https://example.com/photo.jpg')
// From local assets
Image.asset('assets/images/logo.png')
Icon
Renders a Material Design icon from the built-in icon set.
Icon(Icons.star, color: Colors.amber, size: 32)
ElevatedButton
A raised, interactive button with an elevation shadow.
ElevatedButton(
onPressed: () {
print('Button tapped!');
},
child: const Text('Press Me'),
)
Layout Widgets
Layout widgets arrange other widgets on screen. They do not render visible content themselves — they define structure and position.
Column
Arranges children vertically from top to bottom.
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('First'),
Text('Second'),
Text('Third'),
],
)
Row
Arranges children horizontally from left to right.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Left'),
Text('Center'),
Text('Right'),
],
)
Container
A versatile box that applies padding, margins, borders, background color, and size constraints to a single child.
Container(
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.symmetric(vertical: 8.0),
color: Colors.blue,
child: const Text('Inside a container'),
)
Stack
Overlaps children on top of one another, positioned relative to the stack's edges.
Stack(
children: [
Container(color: Colors.blue, width: 200, height: 200),
Positioned(
top: 10,
left: 10,
child: Container(color: Colors.red, width: 80, height: 80),
),
],
)
Input Widgets
Input widgets receive data from the user.
TextField
Allows users to enter text. Use a TextEditingController to read the entered value programmatically.
final TextEditingController _controller = TextEditingController();
TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: 'Enter your name',
border: OutlineInputBorder(),
),
)
Checkbox
Allows the user to select or deselect a boolean option.
bool _checked = false;
Checkbox(
value: _checked,
onChanged: (bool? value) {
setState(() => _checked = value ?? false);
},
)
Radio
Allows selection of a single option from a group.
int _selectedValue = 1;
Radio<int>(
value: 1,
groupValue: _selectedValue,
onChanged: (int? value) {
setState(() => _selectedValue = value!);
},
)
Switch
Toggles between on and off states.
bool _isSwitched = false;
Switch(
value: _isSwitched,
onChanged: (bool value) {
setState(() => _isSwitched = value);
},
)
Form and Validation
Wrap multiple TextField widgets inside a Form widget to enable group validation and submission.
final _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'This field is required';
}
return null; // Valid
},
decoration: const InputDecoration(labelText: 'Email'),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Form is valid, proceed
}
},
child: const Text('Submit'),
),
],
),
)
Validators are functions that return an error message String if the input is invalid, or null if it is valid.
Button Widgets
| Widget | Description |
|---|---|
| ElevatedButton | Raised button with shadow — for primary actions |
| TextButton | Flat button without border — for subtle actions |
| OutlinedButton | Button with a visible border — for secondary actions |
| IconButton | Tappable icon without text — for toolbar actions |
ElevatedButton(onPressed: () {}, child: const Text('Primary'))
TextButton(onPressed: () {}, child: const Text('Secondary'))
OutlinedButton(onPressed: () {}, child: const Text('Outlined'))
IconButton(icon: const Icon(Icons.share), onPressed: () {})
List Widgets
ListView
Renders a scrollable list of widgets. Use ListView.builder for large or dynamic datasets — it creates items lazily as they scroll into view.
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.label),
title: Text(items[index]),
subtitle: Text('Item $index'),
onTap: () => print('Tapped: ${items[index]}'),
);
},
)
Scaffold
The Scaffold widget provides the standard Material Design page structure. It occupies the entire screen and offers slots for the most common layout components.
Scaffold(
appBar: AppBar(title: const Text('Page Title')),
drawer: Drawer(child: ListView(...)),
body: Center(child: Text('Main content')),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
bottomNavigationBar: BottomNavigationBar(items: [...]),
)
| Scaffold Slot | Purpose |
|---|---|
appBar | Top navigation bar with title and actions |
body | Main content area |
drawer | Slide-in side navigation menu |
floatingActionButton | Prominent action button |
bottomNavigationBar | Tab navigation at the bottom |
snackBar | Temporary notification messages |
Responsive UI
Flutter provides tools to build UIs that adapt to different screen sizes and orientations.
// Detect screen orientation
OrientationBuilder(
builder: (context, orientation) {
return GridView.count(
crossAxisCount: orientation == Orientation.portrait ? 2 : 4,
children: itemWidgets,
);
},
)
// Use MediaQuery for screen dimensions
final size = MediaQuery.of(context).size;
final width = size.width;
final height = size.height;
Size hierarchy for child widgets:
Fixed size --> SizedBox(width: 100, height: 50)
Expands --> Expanded(child: ...)
Flexible --> Flexible(child: ..., flex: 2)
Dynamic --> Container (adapts to content)
Best practices for responsive Flutter UIs:
- Use
ExpandedandFlexibleinstead of hardcoded sizes where possible - Apply
MediaQueryto adapt layouts to screen dimensions - Test on multiple device sizes and orientations
- Break large, complex widgets into smaller, focused components