Everything in Flutter is a widget. Learn the most essential widgets you will use in every app.
In Flutter, everything is a widget — text, buttons, images, layouts, padding, animations, even the app itself. The UI is described as a widget tree: a hierarchy of nested widget objects.
| Type | Description | Example |
|---|---|---|
| StatelessWidget | No mutable state; rebuilds only when its parent provides new data | Text, Icon, Image |
| StatefulWidget | Holds mutable state; calls setState() to rebuild | Checkbox, Slider, custom counter |
| InheritedWidget | Passes data down the tree efficiently | Theme, MediaQuery |
A StatelessWidget has one required method: build(). It receives a BuildContext and returns a Widget. It is rebuilt every time its parent rebuilds and passes new constructor arguments.
import 'package:flutter/material.dart';
// The widget class (immutable — all fields must be final)
class GreetingCard extends StatelessWidget {
final String name;
final String message;
// Constructor — use const for performance
const GreetingCard({
super.key,
required this.name,
required this.message,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Text(message),
],
),
),
);
}
}
// Usage
GreetingCard(name: 'Alice', message: 'Welcome back!')
const to constructors and widget instantiations when the values are compile-time constants. This lets Flutter skip rebuilding those subtrees.A StatefulWidget is split into two classes: the widget class (immutable) and the State class (mutable). The State class holds the data and the build() method.
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
// The State class — can hold mutable variables
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0; // underscore prefix = private
bool _isActive = false;
// setState() triggers a rebuild of this widget
void _increment() {
setState(() {
_count++;
_isActive = _count > 0;
});
}
void _reset() {
setState(() {
_count = 0;
_isActive = false;
});
}
// initState — called once when the widget is first created
@override
void initState() {
super.initState();
// initialize things here (e.g., load data, start animations)
}
// dispose — called when the widget is removed from the tree
@override
void dispose() {
// clean up controllers, streams, etc.
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Count: $_count', style: const TextStyle(fontSize: 32)),
Text(_isActive ? 'Active' : 'Zero'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _increment, child: const Text('+')),
const SizedBox(width: 12),
ElevatedButton(onPressed: _reset, child: const Text('Reset')),
],
),
],
);
}
}
The Text widget displays a string. Style it with TextStyle.
// Basic text
const Text('Hello, Flutter!')
// With style
Text(
'Hello, Flutter!',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic,
color: Colors.blue,
letterSpacing: 1.5,
height: 1.4, // line height multiplier
decoration: TextDecoration.underline,
),
)
// Overflow handling (when text is too long)
Text(
'This is a very long text that might overflow the container width...',
maxLines: 2,
overflow: TextOverflow.ellipsis, // show "..." at end
softWrap: true,
)
// RichText — mix multiple styles in one text block
RichText(
text: TextSpan(
style: const TextStyle(color: Colors.black, fontSize: 16),
children: [
const TextSpan(text: 'Hello, '),
TextSpan(
text: 'Flutter',
style: const TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
const TextSpan(text: '!'),
],
),
)
// SelectableText — user can copy the text
const SelectableText('Copy me!')
Container is the most flexible single-child layout widget. It combines sizing, padding, margin, color, decoration, and child alignment.
Container(
// Size
width: 200,
height: 100,
// Spacing
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
margin: const EdgeInsets.all(12),
// Alignment of child inside
alignment: Alignment.center,
// Simple color (cannot use with decoration)
// color: Colors.blue,
// Decoration — for border, borderRadius, gradient, shadow
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.blue, width: 2),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: const Offset(2, 4),
),
],
gradient: const LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Text('Styled Container'),
)
// Shorthand EdgeInsets constructors
EdgeInsets.all(16) // all four sides = 16
EdgeInsets.symmetric(vertical: 8, horizontal: 16)
EdgeInsets.only(top: 8, left: 12)
EdgeInsets.fromLTRB(12, 8, 12, 8) // left, top, right, bottom
color and decoration on the same Container. Put the color inside BoxDecoration when you also need other decoration properties.// Image from asset (declare in pubspec.yaml first)
// pubspec.yaml:
// flutter:
// assets:
// - assets/images/photo.jpg
Image.asset(
'assets/images/photo.jpg',
width: 300,
height: 200,
fit: BoxFit.cover, // how image fills the space
)
// Image from URL
Image.network(
'https://picsum.photos/300/200',
width: 300,
height: 200,
fit: BoxFit.cover,
loadingBuilder: (context, child, progress) {
if (progress == null) return child;
return const Center(child: CircularProgressIndicator());
},
errorBuilder: (context, error, stackTrace) {
return const Icon(Icons.broken_image, size: 64);
},
)
// BoxFit options
// BoxFit.cover — fills box, may crop
// BoxFit.contain — fits inside box, may letterbox
// BoxFit.fill — stretches to fill (may distort)
// BoxFit.fitWidth — fits width, may crop height
// BoxFit.fitHeight — fits height, may crop width
// BoxFit.none — original size, centered
// CircleAvatar — circular image commonly used for profile pictures
CircleAvatar(
radius: 40,
backgroundImage: NetworkImage('https://picsum.photos/80'),
backgroundColor: Colors.grey.shade200,
)
// ElevatedButton — filled, raised button (primary actions)
ElevatedButton(
onPressed: () => print('Tapped!'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: const Text('Submit'),
)
// ElevatedButton with icon
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.send),
label: const Text('Send'),
)
// TextButton — no background (secondary, less prominent)
TextButton(
onPressed: () {},
child: const Text('Cancel'),
)
// OutlinedButton — border only, no fill
OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Colors.blue, width: 2),
),
child: const Text('Learn More'),
)
// IconButton — circular tap area around an icon
IconButton(
onPressed: () {},
icon: const Icon(Icons.favorite),
color: Colors.red,
iconSize: 32,
tooltip: 'Add to favorites',
)
// FloatingActionButton — the circular button usually at bottom-right
FloatingActionButton(
onPressed: () {},
tooltip: 'Add',
child: const Icon(Icons.add),
)
// Small FAB
FloatingActionButton.small(
onPressed: () {},
child: const Icon(Icons.add),
)
// Disable a button by passing null to onPressed
ElevatedButton(
onPressed: null, // null = disabled, greyed out
child: const Text('Disabled'),
)
// Basic icon const Icon(Icons.home) // With size and color Icon( Icons.favorite, size: 48, color: Colors.red, ) // Common Material Icons Icons.home Icons.settings Icons.person Icons.search Icons.menu Icons.close Icons.add Icons.edit Icons.delete Icons.check Icons.arrow_back Icons.arrow_forward Icons.email Icons.phone Icons.lock Icons.visibility Icons.star Icons.share Icons.camera_alt Icons.photo Icons.attach_file Icons.notifications Icons.shopping_cart Icons.logout // Icon with semantic label (accessibility) Icon( Icons.warning, color: Colors.orange, semanticLabel: 'Warning', ) // Icon inside a Circle CircleAvatar( backgroundColor: Colors.blue, child: const Icon(Icons.person, color: Colors.white), )
| Widget | Purpose | Key Properties |
|---|---|---|
| Text | Display string | style, maxLines, overflow |
| Container | Box with size/color/deco | width, height, padding, margin, decoration |
| Image.asset | Local image | fit, width, height |
| Image.network | Remote image | fit, loadingBuilder, errorBuilder |
| ElevatedButton | Primary action button | onPressed, style, child |
| TextButton | Low-emphasis button | onPressed, child |
| OutlinedButton | Outlined button | onPressed, style, child |
| IconButton | Tappable icon | onPressed, icon, color, iconSize |
| Icon | Material icon glyph | Icons.xxx, size, color |
| CircleAvatar | Circular image/icon | radius, backgroundImage, backgroundColor |
| Card | Elevated surface | elevation, shape, child |
| Chip | Label pill | label, avatar, onDeleted |
| Divider | Horizontal separator | thickness, color, indent |
| CircularProgressIndicator | Loading spinner | value, color, strokeWidth |
| LinearProgressIndicator | Progress bar | value, backgroundColor, color |
| Tooltip | Hover/long-press hint | message, child |
| SelectableText | Copyable text | style, maxLines |
| RichText | Multi-style text | text: TextSpan |