Skip to main content

Material 3 design

About 2 min

Material 3 design

In the previous chapters we built the Travel Notes app and cleaned up the structure. The app works, but we can still make the UI more consistent.

Flutter uses Material Design as its default design system. Modern Flutter apps use Material 3, the current version of Google's design system.

Material 3 is not only about colors. It also gives guidance for typography, spacing, components, dark mode, shapes and motion.

Use a Travel Notes theme

We already created a theme in main.dart. Now we can make it a little more complete.

Replace the theme property in MaterialApp with this:

theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.teal,
  ),
  appBarTheme: AppBarTheme(
    backgroundColor: colors.primary,
    foregroundColor: colors.onPrimary,
  ),
  useMaterial3: true,
),
 
 
 
 
 
 
 
 
 
 

Add a dark theme too:

darkTheme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.teal,
    brightness: Brightness.dark,
  ),
  useMaterial3: true,
),
themeMode: ThemeMode.system,
 
 
 
 
 
 
 
 

The full MaterialApp now looks like this:

return MaterialApp(
  debugShowCheckedModeBanner: false,
  title: 'Travel Notes',
  theme: ThemeData(
    colorScheme: colors,
    appBarTheme: AppBarTheme(
      backgroundColor: colors.primary,
      foregroundColor: colors.onPrimary,
    ),
    useMaterial3: true,
  ),
  darkTheme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.teal,
      brightness: Brightness.dark,
    ),
    useMaterial3: true,
  ),
  themeMode: ThemeMode.system,
  home: const AuthGate(),
);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

seedColor is the base color. Flutter generates a complete color scheme from it. For Travel Notes, teal fits the calm travel/journal feeling better than a random default color.

Use theme colors

Inside widgets, use colors from the theme instead of hardcoded colors.

final ColorScheme colors = Theme.of(context).colorScheme;

The TripCard from the architecture chapter already does this:

leading: CircleAvatar(
  backgroundColor: colors.primary,
  foregroundColor: colors.onPrimary,
  child: Text(trip.destination.substring(0, 1)),
),
 
 
 
 
 

The on... colors are designed to be readable on top of the matching background color.

ColorUse for
primaryimportant actions and active elements
secondarysupporting actions
surfaceapp backgrounds, cards and sheets
errorvalidation and destructive feedback
onPrimarytext/icons shown on primary
onSurfacetext/icons shown on surface

Typography

Use the text theme instead of hardcoding every font size.

For example, the empty state in TripListPage can use the theme:

Text(
  "No trips yet",
  style: Theme.of(context).textTheme.titleMedium,
)
 
 
 
 

Common text styles:

StyleUse for
headlineLargeimportant page titles
headlineMediumsection titles
titleLargeapp bars, dialogs, cards
titleMediumempty states and important list text
bodyLargemain readable text
bodyMediumsupporting text
labelLargebuttons and labels

Material 3 components

Prefer modern Material 3 components:

Older patternMaterial 3 pattern
custom filled buttonFilledButton or FilledButton.icon
custom outlined buttonOutlinedButton
custom floating panelBottomSheet
old bottom navigationNavigationBar
custom side menuNavigationRail

In TripDetailPage, the save button from the forms chapter already uses a Material 3 component:

FilledButton.icon(
  onPressed: isSaving ? null : _saveTrip,
  icon: const Icon(Icons.save),
  label: const Text("Save trip"),
)
 
 
 
 
 

This is better than manually styling a button with custom colors, because it automatically follows the app theme.

Spacing and shape

Keep spacing consistent. The Travel Notes pages already use 8, 12 and 16 a lot. That is fine for this course, but you can make the intent clearer with constants:

const double spacingSmall = 8;
const double spacingMedium = 12;
const double spacingLarge = 16;

For example:

Padding(
  padding: const EdgeInsets.all(spacingLarge),
  child: Form(...),
)
 
 
 
 

Do not overdo this in small apps. Constants are useful when they make the code easier to read, not when they make every line longer.

Dark mode

Because themeMode is set to ThemeMode.system, the app follows the user's device setting.

This only works well when pages use theme colors. If a widget hardcodes Colors.black, Colors.white or Colors.red, it might look wrong in dark mode.

For validation or error text, prefer:

Theme.of(context).colorScheme.error

instead of:

Colors.red

Best practices

  • Start with ColorScheme.fromSeed.
  • Use Theme.of(context) instead of hardcoded styles.
  • Add dark mode early.
  • Use Material components before building custom widgets.
  • Keep spacing and border radius consistent.
  • Test screens with light mode, dark mode and large text.

Exercise

Improve the Travel Notes UI:

  • Add the dark theme to main.dart.
  • Make sure useMaterial3: true is enabled.
  • Replace hardcoded error colors with Theme.of(context).colorScheme.error.
  • Use Theme.of(context).textTheme for the empty state in TripListPage.
  • Check the login page, trip list and trip detail page in light and dark mode.