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.
                
                  The ColorScheme.fromSeed constructor generates a ColorScheme
                  derived from the given seedColor. The colors generated by this
                  constructor are designed to work well together and meet contrast
                  requirements for accessibility in the Material 3 Design system.
                
                  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.
                
Code before migration:
theme: ThemeData(
  colorScheme: ColorScheme.light(primary: Colors.blue),
),
                    
                    
                    
                  Code after migration:
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
),
                    
                    
                    
                  
                  To generate a content-based dynamic color scheme, use the
                  ColorScheme.fromImageProvider static method. For an example of generating a
                  color scheme, check out the ColorScheme from a network image
                   sample.
                
                  Changes to Flutter Material 3 include a new background color.
                  ColorScheme.surfaceTint indicates an elevated widget.
                  Some widgets use different colors.
                
To return your app's UI to its previous behavior (which we don't recommend):
- 
                    Set 
Colors.grey[50]!toColorScheme.background(when the theme isBrightness.light). - 
                    Set  
Colors.grey[850]!toColorScheme.background(when the theme isBrightness.dark). 
Code before migration:
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
                    
                    
                    
                  Code after migration:
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
    background: Colors.grey[50]!,
  ),
),
                    
                    
                    
                  darkTheme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.deepPurple,
    brightness: Brightness.dark,
  ).copyWith(background: Colors.grey[850]!),
),
                    
                    
                    
                  
                  The ColorScheme.surfaceTint value indicates a component's elevation in
                  Material 3. Some widgets might use both surfaceTint and shadowColor
                   to
                  indicate elevation (for example, Card and ElevatedButton) and others might
                  only use surfaceTint to indicate elevation (such as AppBar).
                
                  To return to the widget's previous behavior, set, set Colors.transparent
                  to ColorScheme.surfaceTint in the theme. To differentiate a widget's shadow
                  from the content (when it has no shadow), set the ColorScheme.shadow color to
                  the shadowColor property in the widget theme without a default shadow color.
                
Code before migration:
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
                    
                    
                    
                  Code after migration:
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.
                
Code before migration:
ElevatedButton(
  onPressed: () {},
  child: const Text('Button'),
),
                    
                    
                    
                  Code after migration:
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.
                
Code before migration:
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,
  ),
),
                    
                    
                    
                  Code after migration:
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.
                
Code before migration:
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',
    ),
  ],
),
                    
                    
                    
                  Code after migration:
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',
    ),
  ],
),
                    
                    
                    
                  
                  Check out the complete sample on
                  migrating from BottomNavigationBar to NavigationBar.
                
                  Replace the Drawer
                   widget with NavigationDrawer, which provides
                  pill-shaped navigation indicators, rounded corners, and new color mappings.
                
Code before migration:
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: () { },
      ),
    ],
  ),
),
                    
                    
                    
                  Code after migration:
NavigationDrawer(
  children: <Widget>[
    DrawerHeader(
      child: Text(
        'Drawer Header',
        style: Theme.of(context).textTheme.titleLarge,
      ),
    ),
    const NavigationDrawerDestination(
      icon: Icon(Icons.message),
      label: Text('Messages'),
    ),
    const NavigationDrawerDestination(
      icon: Icon(Icons.account_circle),
      label: Text('Profile'),
    ),
    const NavigationDrawerDestination(
      icon: Icon(Icons.settings),
      label: Text('Settings'),
    ),
  ],
),
                    
                    
                    
                  
                  Check out the complete sample on
                  migrating from Drawer to NavigationDrawer.
                
                  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:
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:
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),
      ),
    ],
  ),
),
                    
                    
                    
                  
                  SegmentedButton, an updated version of 
                  ToggleButtons,
                  uses fully rounded corners, differs in layout height and
                  size, and uses a Dart Set to determine selected items.
                
Code before migration:
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),
  ],
),
                    
                    
                    
                  Code after migration:
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,
    ),
  ],
),
                    
                    
                    
                  
                  Check out the complete sample on
                  migrating from ToggleButtons to SegmentedButton.
                
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 
MenuBaror aMenuAnchor. 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 ofPopupMenuButton(and related) classes. - 
                    
DropdownMenucombines 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 forDropdownButtonwidget, although it isn't necessary. - 
                    
SearchBarandSearchAnchorare 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. - 
                    
Badgedecorates its child with a small label of just a few characters. Like '+1'. Badges are typically used to decorate the icon within aNavigationDestination, aNavigationRailDestination, ANavigationDrawerDestination, or a button's icon, as inTextButton.icon. - 
                    
FilledButtonandFilledButton.tonalare very similar to anElevatedButtonwithout the elevation changes and drop shadow. - 
                    
FilterChip.elevated,ChoiceChip.elevated, andActionChip.elevatedare elevated variants of the same chips with a drop shadow and a fill color. - 
                    
Dialog.fullscreenfills 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:
Relevant issues:
Relevant PRs:
Unless stated otherwise, the documentation on this site reflects Flutter 3.35.5. Page last updated on 2025-10-28. View source or report an issue.