Buttons & User Interactions
Making Your App Respond to Taps
Open interactive version (quiz + challenge)Real-world analogy
Buttons are like doorbells — they sit there looking pretty, but the magic happens when someone presses them. The doorbell (button) triggers an action (the ring), and different doorbells (ElevatedButton, TextButton, IconButton) look different but all do the same job: respond to a press!
What is it?
Buttons and interaction widgets are how users communicate with your app. Flutter provides several button types (ElevatedButton, TextButton, IconButton, OutlinedButton, FloatingActionButton) for different visual emphasis levels, plus GestureDetector and InkWell for making any widget respond to touch gestures.
Real-world relevance
Every app screen has buttons. The team_mvp_kit project uses styled ElevatedButtons for primary actions, TextButtons for navigation links, and GestureDetector for custom interactive cards. Choosing the right button type creates clear visual hierarchy that guides users through your app.
Key points
- ElevatedButton — ElevatedButton is the most common button in Flutter. It has a raised, filled appearance with a shadow that gives it depth. Use it for primary actions like 'Submit', 'Save', or 'Continue'. It takes an onPressed callback that fires when tapped and a child widget for its label.
- TextButton — TextButton is a flat button with no elevation or background — just text. Use it for less important actions like 'Cancel', 'Skip', or 'Learn More'. It blends into the UI without drawing too much attention, making it perfect for secondary actions alongside an ElevatedButton.
- IconButton — IconButton displays an icon that responds to taps. It has a circular ink splash effect when pressed. Use it for toolbar actions like back arrows, favorites, share, or delete. It takes an icon property for the Icon widget and onPressed for the callback.
- onPressed Callback — The onPressed property is a function that runs when the user taps the button. If you set onPressed to null, the button becomes disabled — it greys out and ignores taps. This is how you prevent users from submitting a form before filling it out.
- GestureDetector — GestureDetector wraps any widget to detect gestures: taps, double taps, long presses, drags, and swipes. Unlike buttons, it has no visual feedback by default — you control the look entirely. Use it to make images, containers, or custom widgets tappable.
- InkWell for Material Ripple — InkWell is like GestureDetector but adds the Material Design ripple animation when tapped. It must be inside a Material widget to show the ripple effect properly. Use InkWell when you want tap detection with visual feedback on custom widgets.
- Button Styling — Use styleFrom() or ButtonStyle to customize button appearance: background color, text color, padding, shape, elevation, and size. The style property accepts a ButtonStyle object, and the static styleFrom() method provides a convenient shorthand.
- OutlinedButton — OutlinedButton has a transparent background with a visible border. It sits between ElevatedButton (high emphasis) and TextButton (low emphasis) in visual hierarchy. Use it for secondary actions that still need some visual weight, like 'Add to Cart' next to a 'Buy Now' ElevatedButton.
- FloatingActionButton — FloatingActionButton (FAB) is a circular button that floats above the UI, typically placed at the bottom-right of a Scaffold. It represents the primary action of a screen — like composing a new email in Gmail or adding a new item in a to-do app.
Code example
import 'package:flutter/material.dart';
class InteractionDemo extends StatefulWidget {
const InteractionDemo({super.key});
@override
State<InteractionDemo> createState() => _InteractionDemoState();
}
class _InteractionDemoState extends State<InteractionDemo> {
int _counter = 0;
bool _isFavorite = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Buttons Demo'),
actions: [
IconButton(
icon: Icon(
_isFavorite ? Icons.favorite : Icons.favorite_border,
color: _isFavorite ? Colors.red : null,
),
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
},
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Counter: $_counter',
style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => setState(() => _counter++),
child: const Text('Increment'),
),
const SizedBox(height: 12),
OutlinedButton(
onPressed: () => setState(() => _counter--),
child: const Text('Decrement'),
),
const SizedBox(height: 12),
TextButton(
onPressed: () => setState(() => _counter = 0),
child: const Text('Reset'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => _counter += 10),
child: const Icon(Icons.add),
),
);
}
}Line-by-line walkthrough
- 1. Import the Material Design package for all widgets
- 2.
- 3. Define InteractionDemo as a StatefulWidget since buttons will change state
- 4. Pass the key to the parent constructor
- 5.
- 6. Create the mutable State class
- 7. Declare a counter variable starting at 0
- 8. Declare a boolean to track favorite status
- 9.
- 10. The build method returns our UI
- 11. Return a Scaffold for the page structure
- 12. AppBar with a title at the top
- 13. The actions list puts widgets on the right side of the AppBar
- 14. IconButton toggles between filled and outlined heart icon
- 15. Color the heart red when favorited, default otherwise
- 16.
- 17. When the heart IconButton is pressed...
- 18. setState triggers a rebuild with the toggled favorite value
- 19.
- 20.
- 21.
- 22. The body is centered on screen
- 23. Column arranges children vertically
- 24. Center everything along the main axis
- 25.
- 26. Display the counter value using string interpolation
- 27. Style it using the theme's headlineMedium text style
- 28. Add 24 pixels of vertical space
- 29. ElevatedButton (primary action) increments the counter
- 30. setState with arrow function for concise increment
- 31. Label says 'Increment'
- 32. Add 12 pixels of space
- 33. OutlinedButton (secondary action) decrements the counter
- 34. setState decrements the counter
- 35. Label says 'Decrement'
- 36. Add 12 pixels of space
- 37. TextButton (tertiary action) resets the counter
- 38. setState resets counter to 0
- 39. Label says 'Reset'
- 40.
- 41.
- 42.
- 43. FloatingActionButton floats at bottom-right
- 44. Pressing it adds 10 to the counter at once
- 45. Plus icon inside the FAB
- 46.
- 47.
Spot the bug
ElevatedButton(
onPressed: print('Button pressed!'),
child: Text('Press Me'),
)Need a hint?
Look at what onPressed expects versus what you are passing...
Show answer
onPressed expects a function (VoidCallback), but print('Button pressed!') calls print immediately and passes its return value (void) to onPressed. Fix: wrap it in an anonymous function: onPressed: () { print('Button pressed!'); } or onPressed: () => print('Button pressed!')
Explain like I'm 5
Imagine you built a toy robot with buttons on its chest. The big red button (ElevatedButton) makes it walk forward — it sticks out so you notice it first. The small flat label (TextButton) makes it stop — not as attention-grabbing. The round picture button (IconButton) makes it wave. And if you poke the robot anywhere on its belly (GestureDetector), it giggles! Each button looks different but they all do the same thing: wait for you to press them, then do something cool.
Fun fact
Flutter buttons went through a major redesign in Flutter 2.0. The old RaisedButton, FlatButton, and OutlineButton were deprecated in favor of ElevatedButton, TextButton, and OutlinedButton with a much more flexible styling system called ButtonStyle!
Hands-on challenge
Build a screen with four buttons: an ElevatedButton that shows a SnackBar, a TextButton that toggles text between 'ON' and 'OFF', an IconButton that toggles between a filled and outlined heart icon, and a GestureDetector that changes a Container's color on tap. Style each button differently using styleFrom().
More resources
- Buttons - Flutter Widget Catalog (Flutter Official)
- GestureDetector Class (Flutter API)
- ElevatedButton Class (Flutter API)