Migrate To Material 3 - Flutter
Migrate To Material 3 - Flutter
Migrate to Material 3
Stay up to date Breaking changes Migrate to Material 3
Summary
The Material library has been updated to match the Material 3 Design spec. Changes
include new components and component themes, updated component visuals, and much
more. Many of these updates are seamless. You'll see the new version of an affected
widget when recompiling your app against the 3.16 (or later) release. But some manual
work is also required to complete the migration.
Migration guide
Prior to the 3.16 release, you could opt in to the Material 3 changes by setting the
useMaterial3 flag to true. As of the Flutter 3.16 release (November 2023),
useMaterial3 is true by default.
By the way, you can revert to Material 2 behavior in your app by setting the useMaterial3
to false . However, this is just a temporary solution. The useMaterial3 flag and the
Material 2 implementation will eventually be removed as part of Flutter's deprecation
policy.
Colors
The default values for ThemeData.colorScheme are updated to match the Material 3
Design spec.
When updating to the 3.16 release, your UI might look a little strange without the correct
ColorScheme . To fix this, migrate to the ColorScheme generated from the
ColorScheme.fromSeed constructor.
dart
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
dart
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
dart
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith
background: Colors.grey[50]!,
),
),
dart
darkTheme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.deepPurple,
brightness: Brightness.dark,
).copyWith(background: Colors.grey[850]!),
),
dart
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
dart
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith
surfaceTint: Colors.transparent,
),
appBarTheme: AppBarTheme(
elevation: 4.0,
shadowColor: Theme.of(context).colorScheme.shadow,
),
),
The ElevatedButton now styles itself with a new combination of colors. Previously, when
the useMaterial3 flag was set to false, ElevatedButton styled itself with
ColorScheme.primary for the background and ColorScheme.onPrimary for the
foreground. To achieve the same visuals, switch to the new FilledButton widget without
the elevation changes or drop shadow.
dart
ElevatedButton(
onPressed: () {},
child: const Text('Button'),
),
dart
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () {},
child: const Text('Button'),
),
Typography
The default values for ThemeData.textTheme are updated to match the Material 3
defaults. Changes include updated font size, font weight, letter spacing, and line height.
For more details, check out the TextTheme documentation.
As shown in the following example, prior to the 3.16 release, when a Text widget with a
long string using TextTheme.bodyLarge in a constrained layout wrapped the text into two
lines. However, the 3.16 release wraps the text into three lines. If you must achieve the
previous behavior, adjust the text style and, if necessary, the letter spacing.
dart
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
'This is a very long text that should wrap to multiple lines.',
style: Theme.of(context).textTheme.bodyLarge,
),
),
dart
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200),
child: Text(
'This is a very long text that should wrap to multiple lines.',
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
letterSpacing: 0.0,
),
),
),
Components
Some components couldn't merely be updated to match the Material 3 Design spec but
needed a whole new implementation. Such components require manual migration since
the Flutter SDK doesn't know what, exactly, you want.
Replace the Material 2 style BottomNavigationBar widget with the new NavigationBar
widget. It's slightly taller, contains pill-shaped navigation indicators, and uses new color
mappings.
dart
BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
label: 'School',
),
],
),
dart
NavigationBar(
destinations: const <Widget>[
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.business),
label: 'Business',
),
NavigationDestination(
icon: Icon(Icons.school),
label: 'School',
),
],
),
dart
Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text(
'Drawer Header',
style: Theme.of(context).textTheme.titleLarge,
),
),
ListTile(
leading: const Icon(Icons.message),
title: const Text('Messages'),
onTap: () { },
),
ListTile(
leading: const Icon(Icons.account_circle),
title: const Text('Profile'),
onTap: () {},
),
ListTile(
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () { },
),
],
),
),
Material 3 introduces medium and large app bars that display a larger headline before
scrolling. Instead of a drop shadow, ColorScheme.surfaceTint color is used create a
separation from the content when scrolling.
The following code demonstrates how to implement the medium app bar:
dart
CustomScrollView(
slivers: <Widget>[
const SliverAppBar.medium(
title: Text('Title'),
),
SliverToBoxAdapter(
child: Card(
child: SizedBox(
height: 1200,
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 100, 8, 100),
child: Text(
'Here be scrolling content...',
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
),
),
],
),
There are now two types of TabBar widgets: primary and secondary. Secondary tabs are
used within a content area to further separate related content and establish hierarchy.
Check out the TabBar.secondary example.
The new TabBar.tabAlignment property specifies the horizontal alignment of the tabs.
The following sample shows how to modify tab alignment in a scrollable TabBar :
dart
AppBar(
title: const Text('Title'),
bottom: const TabBar(
tabAlignment: TabAlignment.start,
isScrollable: true,
tabs: <Widget>[
Tab(
icon: Icon(Icons.cloud_outlined),
),
Tab(
icon: Icon(Icons.beach_access_sharp),
),
Tab(
icon: Icon(Icons.brightness_5_sharp),
),
],
),
),
dart
enum Weather { cloudy, rainy, sunny }
ToggleButtons(
isSelected: const [false, true, false],
onPressed: (int newSelection) { },
children: const <Widget>[
Icon(Icons.cloud_outlined),
Icon(Icons.beach_access_sharp),
Icon(Icons.brightness_5_sharp),
],
),
dart
enum Weather { cloudy, rainy, sunny }
SegmentedButton<Weather>(
selected: const <Weather>{Weather.rainy},
onSelectionChanged: (Set<Weather> newSelection) { },
segments: const <ButtonSegment<Weather>>[
ButtonSegment(
icon: Icon(Icons.cloud_outlined),
value: Weather.cloudy,
),
ButtonSegment(
icon: Icon(Icons.beach_access_sharp),
value: Weather.rainy,
),
ButtonSegment(
icon: Icon(Icons.brightness_5_sharp),
value: Weather.sunny,
),
],
),
New components
• "Menu bars and cascading menus" provide a desktop-style menu system that is fully
traversable with the mouse or keyboard. Menus are anchored by a MenuBar or a
MenuAnchor . The new menu system isn't something that existing applications must
migrate to, however applications that are deployed on the web or on desktop
platforms should consider using it instead of PopupMenuButton (and related)
classes.
• DropdownMenu combines a text field and a menu to produce what's sometimes
called a combo box. Users can select a menu item from a potentially large list by
entering a matching string or by interacting with the menu with touch, mouse, or
keyboard. This can be a good replacement for DropdownButton widget, although it
isn't necessary.
• SearchBar and SearchAnchor are for interactions where the user enters a search
query, the app computes a list of matching responses, and then the user either
selects one or adjusts the query.
• Badge decorates its child with a small label of just a few characters. Like '+1'. Badges
are typically used to decorate the icon within a NavigationDestination , a
NavigationRailDestination , A NavigationDrawerDestination , or a button's
icon, as in TextButton.icon .
• FilledButton and FilledButton.tonal are very similar to an ElevatedButton
without the elevation changes and drop shadow.
• FilterChip.elevated , ChoiceChip.elevated , and ActionChip.elevated are
elevated variants of the same chips with a drop shadow and a fill color.
• Dialog.fullscreen fills the entire screen and typically contains a title, an action
button, and a close button at the top.
Timeline
In stable release: 3.16
References
Documentation:
API documentation:
• ThemeData.useMaterial3
Relevant issues:
Relevant PRs:
Except as otherwise noted, this site is licensed under a Creative Commons Attribution 4.0 International
License, and code samples are licensed under the 3-Clause BSD License.