In Flutter's Material 3, how do FilledButton, FilledButton.tonal, ElevatedButton, OutlinedButton and TextButton differ, and when do you use each?
answer
- ranked by emphasis
- filled for the final action
- tonal as the middle ground
- text buttons in dialogs
- onPressed null disables
basics
~20 sThey are the same kind of button at different emphasis: FilledButton for the key final action, FilledButton.tonal a step lower, ElevatedButton when a lifted surface is needed, OutlinedButton for medium emphasis and TextButton for the least, such as dialog actions.
solid answer
~40 sAll five extend `ButtonStyleButton`, take `onPressed` and a `child`, and are disabled when `onPressed` is `null`; they differ in emphasis. `FilledButton` has the most visual weight after a floating action button and is meant for the action that completes a flow, such as 'Reserve bike'. `FilledButton.tonal` uses the `secondaryContainer` colour, a middle ground for important but secondary actions like 'Next'. `ElevatedButton` sits on a raised surface, elevation `1.0` in Material 3, useful over busy backgrounds. `OutlinedButton` is medium emphasis, and `TextButton` the lowest, typical for dialog actions. Each has an icon form, such as `FilledButton.icon`, and a `styleFrom` helper for one-off styles. They replaced `FlatButton`, `RaisedButton` and `OutlineButton`, removed after Flutter 2.10. With `useMaterial3` true, the default since 3.16, they all take Material 3 shapes and colours.
code
dart · 20 linesRow(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
const SizedBox(width: 8),
FilledButton.tonal(
onPressed: _canGoBack ? _previousStep : null, // null disables it
child: const Text('Back'),
),
const SizedBox(width: 8),
FilledButton.icon(
onPressed: _reserve,
icon: const Icon(Icons.pedal_bike),
label: const Text('Reserve bike'),
),
],
)go deeper
Recall the order of emphasis: FilledButton, FilledButton.tonal, ElevatedButton and OutlinedButton, then TextButton, and that a null onPressed disables a button.
Explain what they share through ButtonStyleButton, how styleFrom and WidgetStateProperty style one button, and what useMaterial3 changes since 3.16.
Show judgement on a real screen: one filled action per view, tonal for secondary steps, and migrating older ElevatedButton-everywhere code without surprises.
Decide how the team encodes emphasis rules, in component themes or in wrapper widgets, so screens stay consistent as the design evolves.
## One family, different emphasis Material 3 defines buttons by **emphasis**: how strongly a button draws the eye relative to others on the screen. Flutter implements them as subclasses of `ButtonStyleButton`, so they share the same parameters (`onPressed`, `onLongPress`, `style`, `child`) and the same styling model; only their default `ButtonStyle` differs. | Widget | Emphasis | Typical use in a bike-rental app | |---|---|---| | `FilledButton` | highest (after a FAB) | 'Reserve bike', the action that completes the flow | | `FilledButton.tonal` | high, softer colour | 'Next' in the rental steps | | `ElevatedButton` | medium, on a raised surface | a button over the map, which needs separation | | `OutlinedButton` | medium | 'Filters', secondary actions | | `TextButton` | lowest | 'Cancel' and 'Keep bike' in a dialog | The framework's own documentation describes `FilledButton` as having 'the most visual impact after the FloatingActionButton' and suited to 'important, final actions that complete a flow'. `FilledButton.tonal` is described as a middle ground between filled and outlined buttons; its default background comes from the colour scheme's `secondaryContainer`. ## Shared behaviour - **Disabled state.** Passing `onPressed: null` disables the button; there is no separate `enabled` flag. The button draws its disabled colours, and an `ElevatedButton` drops to elevation 0. - **Icons.** Each has an icon constructor: `FilledButton.icon`, `FilledButton.tonalIcon`, `ElevatedButton.icon`, `OutlinedButton.icon`, `TextButton.icon`, taking an `icon` and a `label`. - **One-off styling.** `FilledButton.styleFrom(...)` and its siblings build a `ButtonStyle` from plain values. For state-dependent values, `ButtonStyle` takes `WidgetStateProperty` values (renamed from `MaterialStateProperty` in Flutter 3.22). - **App-wide styling** goes through component themes such as `FilledButtonThemeData`, which belong with the rest of theming. ## ElevatedButton in Material 3 In Material 2, `ElevatedButton` was the default 'raised' button with a noticeable shadow. In Material 3 its default elevation is `1.0` (rising to `3.0` on hover), and it uses a surface colour rather than the primary colour. It is no longer the obvious primary button: for a primary action, `FilledButton` is the Material 3 choice. ## Where useMaterial3 fits `ThemeData.useMaterial3` has defaulted to `true` since Flutter 3.16. It switches components, including these buttons, to Material 3 shapes, colours and elevations, and switches the default colour scheme and typography. Setting it to `false` in the `ThemeData` constructor still opts an app out, but the framework describes it as a temporary flag that will eventually be deprecated, and passing it to `ThemeData.copyWith` is already deprecated because `copyWith` does not recompute the defaults that depend on it. ## History worth recognising 1. `FlatButton`, `RaisedButton` and `OutlineButton` were deprecated in 1.20 and removed after Flutter 2.10. 2. They were replaced by `TextButton`, `ElevatedButton` and `OutlinedButton`, each with its own theme instead of the shared `ButtonTheme`. 3. `FilledButton` and `FilledButton.tonal` arrived later to cover Material 3's filled buttons. In interviews, a candidate who still reaches for `RaisedButton`, or uses `ElevatedButton` as the default primary button in a Material 3 app, is describing an older Flutter. ## A rental flow, button by button 1. The bike-details sheet ends with one `FilledButton`, 'Reserve bike', the only high-emphasis action on that view. 2. Moving between the plan and payment steps uses `FilledButton.tonal` for 'Next', visible but quieter than the final action. 3. 'See pricing' sits beside it as an `OutlinedButton`, a secondary choice. 4. The confirmation dialog uses a `TextButton` for 'Cancel' and a `FilledButton` for 'Reserve', so the safe and the committing choices look different. 5. A 'Locate me' button floating over the map uses `ElevatedButton`, whose surface fill and small elevation separate it from the busy map beneath. One filled button per view is a useful rule of thumb: when two actions both look primary, neither reads as the next step.
- How do you give one FilledButton a different colour without changing every button in the app?Pass `style: FilledButton.styleFrom(backgroundColor: ..., foregroundColor: ...)` on that button. `styleFrom` builds a `ButtonStyle` whose values override the theme's for this button only. For colours that change with states such as pressed or disabled, build a `ButtonStyle` with `WidgetStateProperty` values instead.
- Why is ElevatedButton no longer the default primary button?In Material 3 its role changed: it has a low elevation, `1.0` by default, and a surface-coloured fill, meant for buttons that need separation from a busy background. The strongest emphasis moved to `FilledButton`, which uses the primary colour and is intended for the action that completes a flow.
saying these in an interview costs you the question
- RaisedButton is still the standard raised button in current Flutter.
- ElevatedButton is the Material 3 choice for every primary action.
- Disable a button by wrapping it in IgnorePointer rather than passing null.
- useMaterial3 must be set to true explicitly to get Material 3 buttons.
- Set useMaterial3 through ThemeData.copyWith to switch an existing theme.