flutter
/

Flutter Theme Tutorial for Beginners

Last Sync: Today

On this page

12
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

Flutter Theme Tutorial for Beginners

What is Theme in Flutter?

Theme is a powerful system in Flutter that allows you to define and apply consistent styling across your entire app. It centralizes colors, typography, shapes, and other visual properties, making it easy to maintain a uniform look and feel. With themes, you can switch between light and dark modes, customize individual components, and even create your own theme extensions.

Basic Usage: ThemeData

Themes are typically defined in the MaterialApp widget using the theme (for light mode) and darkTheme parameters. You provide a ThemeData object that describes the colors, fonts, and shapes for your app.

DARTRead-only
1
MaterialApp(
  theme: ThemeData(
    primaryColor: Colors.blue,
    brightness: Brightness.light,
    fontFamily: 'Roboto',
  ),
  darkTheme: ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.teal,
  ),
  themeMode: ThemeMode.system, // follows system setting
  home: MyHomePage(),
)

Now throughout your app, you can access the current theme using Theme.of(context).

Accessing Theme Properties

Once you've defined a theme, you can use Theme.of(context) to retrieve the current theme data and use its properties. This is especially useful for colors and text styles.

DARTRead-only
1
Container(
  color: Theme.of(context).primaryColor,
  child: Text(
    'Hello',
    style: Theme.of(context).textTheme.headlineMedium,
  ),
)

Key Properties of ThemeData

  • brightness: Brightness.light or Brightness.dark. Influences default colors of many widgets.
  • primaryColor: The primary background color for app bars, buttons, etc.
  • colorScheme: A modern way to define colors. Replaces the older primaryColor, accentColor, etc. It includes primary, secondary, surface, error, and more.
  • textTheme: Defines the default text styles (displayLarge, headlineMedium, bodyLarge, etc.).
  • fontFamily: Default font for text.
  • appBarTheme: Customizes the appearance of AppBars.
  • buttonTheme: Customizes button styles (older approach; prefer elevatedButtonTheme).
  • elevatedButtonTheme, textButtonTheme, outlinedButtonTheme: Specific button themes.
  • inputDecorationTheme: For TextFields and forms.
  • cardTheme: For Cards.
  • dividerTheme: For Dividers.
  • iconTheme: Default icon color and size.

Using ColorScheme (Modern Approach)

Flutter now recommends using colorScheme for theming colors. It provides a complete set of semantic colors that work well with both light and dark themes. You can create one easily with ColorScheme.fromSeed().

DARTRead-only
1
theme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.blue,
    brightness: Brightness.light,
  ),
  useMaterial3: true, // enable Material 3 design
)

This automatically generates a full color scheme based on your seed color, including primary, secondary, tertiary, and surface variants.

Light and Dark Theme Switching

To support both light and dark modes, define both theme and darkTheme, then set themeMode. You can also let users toggle the mode by changing the themeMode dynamically.

DARTRead-only
1
MaterialApp(
  theme: ThemeData.light(), // built-in light theme
  darkTheme: ThemeData.dark(), // built-in dark theme
  themeMode: ThemeMode.dark, // force dark mode
)

For more control, you can customize the dark theme by copying the light theme and adjusting brightness:

DARTRead-only
1
darkTheme: ThemeData(
  colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.blue,
    brightness: Brightness.dark,
  ),
)

Text Themes

textTheme defines the typography scale for your app. You can override individual styles while keeping others.

DARTRead-only
1
theme: ThemeData(
  textTheme: TextTheme(
    headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
    bodyMedium: TextStyle(fontSize: 16, color: Colors.grey[800]),
  ),
)

Theme Extensions

For custom app‑specific styles, you can create a ThemeExtension. This allows you to add your own properties to the theme and access them with Theme.of(context).extension<MyColors>().

DARTRead-only
1
class MyColors extends ThemeExtension<MyColors> {
  final Color brandColor;
  final Color dangerColor;

  MyColors({required this.brandColor, required this.dangerColor});

  @override
  MyColors copyWith({Color? brandColor, Color? dangerColor}) {
    return MyColors(
      brandColor: brandColor ?? this.brandColor,
      dangerColor: dangerColor ?? this.dangerColor,
    );
  }

  @override
  MyColors lerp(ThemeExtension<MyColors>? other, double t) {
    if (other is! MyColors) return this;
    return MyColors(
      brandColor: Color.lerp(brandColor, other.brandColor, t)!,
      dangerColor: Color.lerp(dangerColor, other.dangerColor, t)!,
    );
  }
}

// Usage in ThemeData
ThemeData(
  extensions: [MyColors(brandColor: Colors.purple, dangerColor: Colors.red)],
)

// Access later
final myColors = Theme.of(context).extension<MyColors>()!;
Container(color: myColors.brandColor);

Local Theme Overrides

Sometimes you want to change the theme for a specific part of the widget tree. Use Theme widget with a copy of the current theme.

DARTRead-only
1
Theme(
  data: Theme.of(context).copyWith(
    textTheme: TextTheme(bodyMedium: TextStyle(color: Colors.red)),
  ),
  child: SomeWidget(),
)

Common Mistakes Beginners Make

  • Not using Theme.of(context) – Hardcoding colors instead of using theme values makes your app hard to maintain and prevents easy theme switching.
  • Confusing primaryColor with colorScheme.primary: The modern approach uses colorScheme. If you set both, they might conflict. Prefer colorScheme.
  • Forgetting to set useMaterial3: Material 3 is the default for new Flutter apps, but you need to enable it explicitly if you want the latest design.
  • Overriding too much: You don't need to redefine every property. Start from a base theme and override only what you need.
  • Not providing a dark theme: Users expect dark mode support; if you don't provide one, your app may look broken in dark mode.
  • Using Theme.of(context) before the theme is available: Ensure your BuildContext is under a MaterialApp that provides the theme.

Key Points to Remember

  • Define themes in MaterialApp using theme and darkTheme.
  • Use Theme.of(context) to access the current theme anywhere.
  • Prefer colorScheme for defining colors; it's the modern and flexible approach.
  • Text styles are managed via textTheme; use predefined names (headlineMedium, bodyLarge, etc.).
  • For custom properties, create a ThemeExtension.
  • Override themes locally with the Theme widget and copyWith.
  • Always consider both light and dark themes for a polished app.

Common Interview Questions

  1. How does theme inheritance work in Flutter?
  2. What is the difference between ThemeData and Theme widget?
  3. Explain the purpose of colorScheme and how it differs from using primaryColor directly.
  4. How would you implement a dark mode toggle in your app?
  5. What are ThemeExtensions and when would you use them?
  6. How can you override the text style for all buttons without changing the global text theme?

Try it yourself

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Theme Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.teal,
          brightness: Brightness.light,
        ),
        useMaterial3: true,
        textTheme: TextTheme(
          headlineMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
          bodyLarge: TextStyle(fontSize: 16),
        ),
      ),
      darkTheme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.teal,
          brightness: Brightness.dark,
        ),
        useMaterial3: true,
      ),
      themeMode: ThemeMode.system,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Theme Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Current Theme',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            SizedBox(height: 20),
            Text(
              'Primary color: ${Theme.of(context).colorScheme.primary}',
              style: Theme.of(context).textTheme.bodyLarge,
            ),
            SizedBox(height: 10),
            Text(
              'Brightness: ${Theme.of(context).brightness}',
              style: Theme.of(context).textTheme.bodyLarge,
            ),
            SizedBox(height: 30),
            ElevatedButton(
              onPressed: () {},
              child: Text('Sample Button'),
            ),
          ],
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 3

How do you access the current theme's primary color?

A
Theme.of(context).primaryColor
B
Theme.of(context).colorScheme.primary
C
Both are valid, but colorScheme is the modern approach
D
MediaQuery.of(context).primaryColor
Q2
of 3

What parameter in MaterialApp allows you to switch between light and dark themes based on the system setting?

A
themeMode: ThemeMode.system
B
darkTheme: ThemeData.dark()
C
themeMode: ThemeMode.followSystem
D
systemTheme: true
Q3
of 3

Which class would you use to add custom, app‑specific properties to the theme?

A
ThemeExtension
B
CustomTheme
C
AppTheme
D
ThemeData.custom

Previous

flutter navigator

Next

flutter scaffold

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.