Coding Liquids recordings make M2-to-M3 spacing and surface changes visible side by side.
Recognise the Material 3 baseline
Modern Flutter uses Material 3 defaults, including updated component shapes, typography, state layers, and colour roles. Set useMaterial3 explicitly during a migration so intent remains clear across SDK upgrades.
Assuming only colours change overlooks layout and component behaviour differences.
flutter pub add dynamic_color
Build light and dark tonal schemes
ColorScheme.fromSeed can generate both brightness variants from one brand seed. Use surfaceContainer roles and matching on-colours instead of legacy background assumptions.
Copying an M2 primarySwatch into M3 leaves many tonal roles unspecified.
dependencies:
flutter:
sdk: flutter
dynamic_color: any
Instagram component comparisons surface Material 3 changes in navigation, colour roles, shape, and state.
Adopt platform dynamic colour selectively
dynamic_color exposes DynamicColorBuilder with light and dark system schemes where supported. Harmonise or fall back to brand ColorSchemes when the platform supplies no dynamic palette.
Dynamic colour can weaken fixed brand identity, so make the product decision explicit.

Build production-ready Material 3 features
The Complete Flutter Guide turns material 3 into maintainable app architecture, polished UI, and testable production code.
Enrol nowImport dynamic_color for DynamicColorBuilder while retaining a seeded fallback for unsupported platforms.
import 'package:dynamic_color/dynamic_color.dart';
Use NavigationBar FilledButton SegmentedButton and Badge
NavigationBar replaces many BottomNavigationBar use cases, FilledButton represents prominent actions, SegmentedButton handles bounded choices, and Badge annotates icons. Give every destination and segment clear labels and maintain selected state outside the component.
Replacing widgets mechanically without checking semantics and density can regress navigation.
final scheme = ColorScheme.fromSeed(
seedColor: const Color(0xFF6750A4),
brightness: Brightness.light,
);
MaterialApp(
theme: ThemeData(useMaterial3: true, colorScheme: scheme),
home: Scaffold(
bottomNavigationBar: NavigationBar(
selectedIndex: 0,
destinations: const [
NavigationDestination(icon: Icon(Icons.home_outlined), label: 'Home'),
NavigationDestination(icon: Badge(child: Icon(Icons.inbox_outlined)), label: 'Inbox'),
],
),
),
);
Understand tonal elevation and surface tint
Material 3 conveys elevation partly through surface tones and surfaceTintColor in addition to shadows. Use surface containers and component defaults before layering custom grey shadows.
Setting every surfaceTintColor transparent can flatten the hierarchy the theme is designed to express.
SegmentedButton<ViewMode>(
segments: const [
ButtonSegment(value: ViewMode.list, icon: Icon(Icons.view_list), label: Text('List')),
ButtonSegment(value: ViewMode.grid, icon: Icon(Icons.grid_view), label: Text('Grid')),
],
selected: {viewMode},
onSelectionChanged: (values) => setState(() => viewMode = values.single),
)
FilledButton.icon(onPressed: createItem, icon: const Icon(Icons.add), label: const Text('Create'))
My LinkedIn migration notes favour behavioural audits and shared tokens over screenshot chasing.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowMigrate one component family at a time
Enable M3 in a branch, capture golden screenshots, then review navigation, buttons, forms, cards, dialogs, and typography systematically. Add temporary component-theme overrides only where a deliberate design decision differs.
A single flag change shipped without visual regression testing can alter spacing across the whole app.
DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) {
final light = lightDynamic?.harmonized() ??
ColorScheme.fromSeed(seedColor: brand, brightness: Brightness.light);
final dark = darkDynamic?.harmonized() ??
ColorScheme.fromSeed(seedColor: brand, brightness: Brightness.dark);
return MaterialApp(
theme: ThemeData(useMaterial3: true, colorScheme: light),
darkTheme: ThemeData(useMaterial3: true, colorScheme: dark),
home: const Scaffold(body: SizedBox.shrink()),
);
},
)
Migrate by behaviour, not by screenshot
Material 3 changes component defaults, colour roles, typography, shapes, elevation treatment, and navigation patterns. Begin with a semantic ColorScheme and render an inventory of the application’s actual components. This reveals where a local hard-coded style is fighting the theme and where a new default changes layout. Toggling the design mode without auditing those exceptions produces a mixture that is harder to maintain than either system alone.
Seed-generated colour is a starting point, not an accessibility guarantee. Review all foreground and container pairings, error states, disabled controls, selected states, and custom illustrations. Brand colours may need explicit schemes when generation cannot preserve required relationships. Keep colour role names in code so later palette changes do not require hunting for visual hex values embedded throughout feature widgets.
Navigation choices should follow destination count and available width. A bottom navigation treatment, navigation rail, and drawer can represent the same destinations at different breakpoints while preserving route state and semantics. Component changes can alter tap-target size, label visibility, and focus treatment, so validate keyboard and screen-reader behaviour as carefully as appearance. Avoid rebuilding deprecated visuals entirely from containers just to match an old screenshot; first check whether a component theme can express the intentional brand difference.
Roll the migration through a representative route, shared theme layer, and regression suite before applying it everywhere. Compare light and dark modes, narrow and wide screens, text scaling, hover, focus, pressed, selected, disabled, and error states. Golden differences should be reviewed rather than blindly accepted, because some are desired defaults and others are clipped content. Profile complex surfaces after the change, especially if custom elevation or animation was added to imitate previous behaviour. A written component decision record keeps the next feature from reopening the same styling questions.
Density and platform adaptation still matter within a Material 3 design. Pointer-driven desktop layouts may need hover feedback and efficient information density without shrinking touch targets on phones. Check menus, text fields, date pickers, snackbars, dialogs, and selection controls, not only prominent buttons and navigation. If a product uses custom components, assign them the same semantic colour, shape, typography, and state vocabulary. Migration is complete when feature teams can build the next screen from shared components without copying override code from the showcase route.
A component inventory should include the awkward states that marketing screenshots omit. Render text fields with validation and helper copy, menus near screen edges, dialogs with long translated actions, snackbars above navigation, chips wrapping at large text, and selection controls under keyboard focus. Compare those examples before and after migration, then decide whether each difference is a welcome platform-aligned default or a product regression. Record the intentional override in the component theme instead of fixing it separately on every route.
Adaptive layouts can share Material roles while changing navigation presentation. Preserve destination IDs and nested stacks as a phone moves from a navigation bar to a rail or drawer, and keep selected, hover, focus, and disabled states legible in every form. Dynamic colour should enter through a reviewed scheme boundary so unsupported platforms receive an equally complete fallback. Tests should combine brightness, width, text scale, directionality, and restored navigation state. The strongest completion criterion is mundane: a new feature can use the shared scheme and components without knowing which migration exceptions were once necessary.
Common mistakes
- Recognise the Material 3 baseline: In Material 3 theming, assuming only colours change overlooks layout and component behaviour differences; inspect this Material 3 theming cause before changing another Material 3 theming widget.
- Build light and dark tonal schemes: In Material 3 theming, copying an M2 primarySwatch into M3 leaves many tonal roles unspecified; inspect this Material 3 theming cause before changing another Material 3 theming widget.
- Adopt platform dynamic colour selectively: In Material 3 theming, dynamic colour can weaken fixed brand identity, so make the product decision explicit; inspect this Material 3 theming cause before changing another Material 3 theming widget.
- Use NavigationBar FilledButton SegmentedButton and Badge: In Material 3 theming, replacing widgets mechanically without checking semantics and density can regress navigation; inspect this Material 3 theming cause before changing another Material 3 theming widget.
- Understand tonal elevation and surface tint: In Material 3 theming, setting every surfaceTintColor transparent can flatten the hierarchy the theme is designed to express; inspect this Material 3 theming cause before changing another Material 3 theming widget.
- Migrate one component family at a time: In Material 3 theming, a single flag change shipped without visual regression testing can alter spacing across the whole app; inspect this Material 3 theming cause before changing another Material 3 theming widget.
Frequently asked questions
How does recognise the Material 3 baseline work in Material 3 theming?
For Material 3 theming, the starting rule is that modern Flutter uses Material 3 defaults, including updated component shapes, typography, state layers, and colour roles. Apply this Material 3 theming rule first because recognise the Material 3 baseline determines whether the Material 3 theming pattern fits.
Why does adopt platform dynamic colour selectively matter for Material 3 theming?
In Material 3 theming, harmonise or fall back to brand ColorSchemes when the platform supplies no dynamic palette. Keeping adopt platform dynamic colour selectively at the Material 3 theming call site exposes the Material 3 theming return value directly.
What failure should I test first in Material 3 theming?
First reproduce the Material 3 theming case where setting every surfaceTintColor transparent can flatten the hierarchy the theme is designed to express. The reviewed surface keeps tonal elevation where it communicates hierarchy and changes tint only for a documented design reason.
How can I verify Material 3 theming before release?
Exercise migrate one component family at a time with real Material 3 theming inputs on every shipped platform. Inspect the final Material 3 theming callback or output; a successful Material 3 theming button tap alone is not proof.
Further reads
Connect Material 3 theming to the surrounding Flutter stack through these published tutorials:
- Flutter Development Guide 2026
- Flutter ThemeData: App-Wide Colours and Typography
- Flutter Dark Mode: Light/Dark Theme Switching
- Flutter Material Widgets: The Complete Catalogue With Examples
- Flutter BoxShadow and Elevation: Depth Done Right
Sources: Flutter framework and Dart API documentation; Material 3 theming examples verified against current stable Flutter.