Skip to content
Boring Doc
BreakingHigh impactOfficial migration guidesince 1.20.0landed 1.20.0-0.0.pre

New Buttons and Button Themes

Framework · Official source

A new set of basic material button widgets and themes have been added to Flutter. The original classes have been deprecated and will eventually be removed. The overall goal is to make buttons more flexible, and easier to configure via constructor parameters or themes.

Original behavior

Rather than try and evolve the existing button classes and their theme in-place, we have introduced new replacement button widgets and themes. In addition to freeing us from the backwards compatibility labyrinth that evolving the existing classes in-place would entail, the new names sync Flutter back up with the Material Design spec, which uses the new names for the button components.

New behavior

Not stated in the source.

Who is affected
Applications and packages relying on the previous behaviour or signature. Code that uses FlatButton, RaisedButton, OutlineButton and related APIs.
Practical impact
Behaviour or API differences that can break builds or change runtime behaviour.
What action should I take?
Follow the official migration guide below, update affected call sites, run `dart fix --apply` where a fix is available, then run your tests.
Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)

Use the following information to migrate your buttons to the new API.

Restoring the original button visuals

In many cases it's possible to just switch from the old button class to the new one. That's assuming that the small changes in size/shape and the likely bigger change in colors, aren't a concern.

To preserve the original buttons' appearance in these cases, one can define button styles that match the original as closely as you like. For example, the following style makes a TextButton look like a default FlatButton:

final ButtonStyle flatButtonStyle = TextButton.styleFrom(
  foregroundColor: Colors.black87,
  minimumSize: Size(88, 36),
  padding: EdgeInsets.symmetric(horizontal: 16),
  shape: const RoundedRectangleBorder(
    borderRadius: BorderRadius.all(Radius.circular(2)),
  ),
);

TextButton(
  style: flatButtonStyle,
  onPressed: () { },
  child: Text('Looks like a FlatButton'),
)

Similarly, to make an ElevatedButton look like a default RaisedButton:

final ButtonStyle raisedButtonStyle = ElevatedButton.styleFrom(
  foregroundColor: Colors.black87,
  backgroundColor: Colors.grey[300],
  minimumSize: Size(88, 36),
  padding: EdgeInsets.symmetric(horizontal: 16),
  shape: const RoundedRectangleBorder(
    borderRadius: BorderRadius.all(Radius.circular(2)),
  ),
);
ElevatedButton(
  style: raisedButtonStyle,
  onPressed: () { },
  child: Text('Looks like a RaisedButton'),
)

The OutlineButton style for OutlinedButton is a little more complicated because the outline's color changes to the primary color when the button is pressed. The outline's appearance is defined by a BorderSide and you'll use a MaterialStateProperty to define the pressed outline color:

final ButtonStyle outlineButtonStyle = OutlinedButton.styleFrom(
  foregroundColor: Colors.black87,
  minimumSize: Size(88, 36),
  padding: EdgeInsets.symmetric(horizontal: 16),
  shape: const RoundedRectangleBorder(
    borderRadius: BorderRadius.all(Radius.circular(2)),
  ),
).copyWith(
  side: MaterialStateProperty.resolveWith<BorderSide?>(
    (Set<MaterialState> states) {
      if (states.contains(MaterialState.pressed)) {
        return BorderSide(
          color: Theme.of(context).colorScheme.primary,
          width: 1,
        );
      }
      return null;
    },
  ),
);

OutlinedButton(
  style: outlineButtonStyle,
  onPressed: () { },
  child: Text('Looks like an OutlineButton'),
)

To restore the default appearance for buttons throughout an application, you can configure the new button themes in the application's theme:

MaterialApp(
  theme: ThemeData.from(colorScheme: ColorScheme.light()).copyWith(
    textButtonTheme: TextButtonThemeData(style: flatButtonStyle),
    elevatedButtonTheme: ElevatedButtonThemeData(style: raisedButtonStyle),
    outlinedButtonTheme: OutlinedButtonThemeData(style: outlineButtonStyle),
  ),
)

To restore the default appearance for buttons in part of an application you can wrap a widget subtree with TextButtonTheme, ElevatedButtonTheme, or OutlinedButtonTheme. For example:

TextButtonTheme(
  data: TextButtonThemeData(style: flatButtonStyle),
  child: myWidgetSubtree,
)

Migrating buttons with custom colors

The following sections cover use of the following FlatButton, RaisedButton, and OutlineButton color parameters:

textColor
disabledTextColor
color
disabledColor
focusColor
hoverColor
highlightColor*
splashColor

The new button classes do not support a separate highlight color because it's no longer part of the Material Design.

Migrating buttons with custom foreground and background colors

Two common customizations for the original button classes are a custom foreground color for FlatButton, or custom foreground and background colors for RaisedButton. Producing the same result with the new button classes is simple:

FlatButton(
  textColor: Colors.red, // foreground
  onPressed: () { },
  child: Text('FlatButton with custom foreground/background'),
)

TextButton(
  style: TextButton.styleFrom(
    foregroundColor Colors.red,
  ),
  onPressed: () { },
  child: Text('TextButton with custom foreground'),
)

In this case the TextButton's foreground (text/icon) color as well as its hovered/focused/pressed overlay colors will be based on Colors.red. By default, the TextButton's background fill color is transparent.

Migrating a RaisedButton with custom foreground and background colors:

RaisedButton(
  color: Colors.red, // background
  textColor: Colors.white, // foreground
  onPressed: () { },
  child: Text('RaisedButton with custom foreground/background'),
)

ElevatedButton(
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.red,
    foregroundColor: Colors.white,
  ),
  onPressed: () { },
  child: Text('ElevatedButton with custom foreground/background'),
)

In this case the button's use of the color scheme's primary color is reversed relative to the TextButton: primary is button's background fill color and onPrimary is the foreground (text/icon) color.

Migrating buttons with custom overlay colors

Overriding a button's default focused, hovered, highlighted, or splash colors is less common. The FlatButton, RaisedButton, and OutlineButton classes have individual parameters for these state-dependent colors. The new TextButton, ElevatedButton, and OutlinedButton classes use a single MaterialStateProperty<Color> parameter instead. The new buttons allow one to specify state-dependent values for all of the colors, the original buttons only supported specifying what's now called the "overlayColor".

FlatButton(
  focusColor: Colors.red,
  hoverColor: Colors.green,
  splashColor: Colors.blue,
  onPressed: () { },
  child: Text('FlatButton with custom overlay colors'),
)

TextButton(
  style: ButtonStyle(
    overlayColor: MaterialStateProperty.resolveWith&#x3C;Color?>(
      (Set&#x3C;MaterialState> states) {
        if (states.contains(MaterialState.focused))
          return Colors.red;
        if (states.contains(MaterialState.hovered))
            return Colors.green;
        if (states.contains(MaterialState.pressed))
            return Colors.blue;
        return null; // Defer to the widget's default.
    }),
  ),
  onPressed: () { },
  child: Text('TextButton with custom overlay colors'),
)

The new version is more flexible although less compact. In the original version, the precedence of the different states is implicit (and undocumented) and fixed, in the new version, it's explicit. For an app that specified these colors frequently, the easiest migration path would be to define one or more ButtonStyles that match the example above - and just use the style parameter - or to define a stateless wrapper widget that encapsulated the three color parameters.

Migrating buttons with custom disabled colors

This is a relatively rare customization. The FlatButton, RaisedButton, and OutlineButton classes have disabledTextColor and disabledColor parameters that define the background and foreground colors when the button's onPressed callback is null.

By default, all of the buttons use the color scheme's disabledForegroundColor color, with opacity 0.38 for the disabled foreground color. Only ElevatedButton has a non-transparent background color and its default value is the disabledForegroundColor color with opacity 0.12. So in many cases one can just use the styleFrom method to override the disabled colors:

RaisedButton(
  disabledColor: Colors.red.withOpacity(0.12),
  disabledTextColor: Colors.red.withOpacity(0.38),
  onPressed: null,
  child: Text('RaisedButton with custom disabled colors'),
),

ElevatedButton(
  style: ElevatedButton.styleFrom(disabledForegroundColor: Colors.red),
  onPressed: null,
  child: Text('ElevatedButton with custom disabled colors'),
)

For complete control over the disabled colors, one must define the ElevatedButton's style explicitly, in terms of MaterialStateProperties:

RaisedButton(
  disabledColor: Colors.red,
  disabledTextColor: Colors.blue,
  onPressed: null,
  child: Text('RaisedButton with custom disabled colors'),
)

ElevatedButton(
  style: ButtonStyle(
    backgroundColor: MaterialStateProperty.resolveWith&#x3C;Color?>(
      (Set&#x3C;MaterialState> states) {
        if (states.contains(MaterialState.disabled))
          return Colors.red;
        return null; // Defer to the widget's default.
    }),
    foregroundColor: MaterialStateProperty.resolveWith&#x3C;Color?>(
      (Set&#x3C;MaterialState> states) {
        if (states.contains(MaterialState.disabled))
          return Colors.blue;
        return null; // Defer to the widget's default.
    }),
  ),
  onPressed: null,
  child: Text('ElevatedButton with custom disabled colors'),
)

As with the previous case, there are obvious ways to make the new version more compact in an app where this migration comes up often.

Migrating buttons with custom elevations

This is also a relatively rare customization. Typically, only ElevatedButtons (originally called RaisedButtons) include elevation changes. For elevations that are proportional to a baseline elevation (per the Material Design specification), one can override all of them quite simply.

By default, a disabled button's elevation is 0, and the remaining states are defined relative to a baseline of 2:

disabled: 0
hovered or focused: baseline + 2
pressed: baseline + 6

So to migrate a RaisedButton for which all elevations have been defined:

RaisedButton(
  elevation: 2,
  focusElevation: 4,
  hoverElevation: 4,
  highlightElevation: 8,
  disabledElevation: 0,
  onPressed: () { },
  child: Text('RaisedButton with custom elevations'),
)

ElevatedButton(
  style: ElevatedButton.styleFrom(elevation: 2),
  onPressed: () { },
  child: Text('ElevatedButton with custom elevations'),
)

To arbitrarily override just one elevation, like the pressed elevation:

RaisedButton(
  highlightElevation: 16,
  onPressed: () { },
  child: Text('RaisedButton with a custom elevation'),
)

ElevatedButton(
  style: ButtonStyle(
    elevation: MaterialStateProperty.resolveWith&#x3C;double?>(
      (Set&#x3C;MaterialState> states) {
        if (states.contains(MaterialState.pressed))
          return 16;
        return null;
      }),
  ),
  onPressed: () { },
  child: Text('ElevatedButton with a custom elevation'),
)

Migrating buttons with custom shapes and borders

The original FlatButton, RaisedButton, and OutlineButton classes all provide a shape parameter which defines both the button's shape and the appearance of its outline. The corresponding new classes and their themes support specifying the button's shape and its border separately, with OutlinedBorder shape and BorderSide side parameters.

In this example the original OutlineButton version specifies the same color for border in its highlighted (pressed) state as for other states.

OutlineButton(
  shape: StadiumBorder(),
  highlightedBorderColor: Colors.red,
  borderSide: BorderSide(
    width: 2,
    color: Colors.red
  ),
  onPressed: () { },
  child: Text('OutlineButton with custom shape and border'),
)

OutlinedButton(
  style: OutlinedButton.styleFrom(
    shape: StadiumBorder(),
    side: BorderSide(
      width: 2,
      color: Colors.red
    ),
  ),
  onPressed: () { },
  child: Text('OutlinedButton with custom shape and border'),
)

Most of the new OutlinedButton widget's style parameters, including its shape and border, can be specified with MaterialStateProperty values, which is to say that they can have different values depending on the button's state. To specify a different border color when the button is pressed, do the following:

OutlineButton(
  shape: StadiumBorder(),
  highlightedBorderColor: Colors.blue,
  borderSide: BorderSide(
    width: 2,
    color: Colors.red
  ),
  onPressed: () { },
  child: Text('OutlineButton with custom shape and border'),
)

OutlinedButton(
  style: ButtonStyle(
    shape: MaterialStateProperty.all&#x3C;OutlinedBorder>(StadiumBorder()),
    side: MaterialStateProperty.resolveWith&#x3C;BorderSide>(
      (Set&#x3C;MaterialState> states) {
        final Color color = states.contains(MaterialState.pressed)
          ? Colors.blue
          : Colors.red;
        return BorderSide(color: color, width: 2);
      }
    ),
  ),
  onPressed: () { },
  child: Text('OutlinedButton with custom shape and border'),
)
Migration assistantAI-generated · grounded in cited sources
AI explanations are not configured
Set AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.
Official migration guide·Verified classification (95%)

Listed in the official breaking-changes index (docs.flutter.dev/release/breaking-changes).

An official announcement or migration guide documents an intended behaviour or API change. A verified source change (git diff) shows code changed but does not by itself prove a public API is breaking. AI interpretations are labelled and never used to classify a change.

Source commit
b3045439b7c0
Source last updated
Oct 21, 2026
Last synchronized
Oct 21, 2026
Publication status
published
Publication history
  • Oct 21, 2026, 8:31 PM: new → published (ingestion) — Backed by an official breaking-change/migration guide.
AI explanationAI-generated · grounded in cited sources
AI explanations are not configured
Set AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.
Was this useful?