Skip to content

Software Development at Program Tom LTD

Place for coding, programming, development and software in general.

Menu
  • Blog
  • PDF Booklets
  • Dev Utils & Content
  • Java Spring Boot Or Web Apps
  • English
    • български
    • English
    • Español
    • Português
    • हिन्दी
    • Русский
    • Deutsch
    • Français
    • Italiano
    • العربية
  • About Us
Menu
How to implement theming with Flutter?

How to implement theming with Flutter?

Posted on October 26, 2025 by Toma Velev

Theming in Flutter is primarily implemented using the ThemeData class and the Theme widget, allowing you to define a consistent look and feel across your entire application.

🎨 Implementing Theming in Flutter

  1. Define ThemeData: Create one or more instances of the ThemeData class, typically for a light and a dark theme. The most important properties to set for a modern theme are:
    • colorScheme: This is the heart of Material Design 3 (M3) theming. Use ColorScheme.fromSeed() with a single seedColor to automatically generate a harmonious color palette that adheres to M3 guidelines.
    • useMaterial3: Set this to true (it’s the default since Flutter 3.16) to adopt the latest M3 components, typography, and color systems.
    • textTheme: Define the text styling (like font family, size, and weight) for various text roles (e.g., displayLarge, titleMedium, bodySmall). You can use packages like google_fonts here for custom typography.
    • Widget Themes (e.g., appBarTheme, elevatedButtonTheme): Customize the default appearance of specific widgets beyond the color scheme.
  2. Apply to MaterialApp: Pass your light and dark ThemeData objects to the MaterialApp widget in your app’s root:
    MaterialApp(
      title: 'My Themed App',
      theme: lightTheme, // Your light ThemeData
      darkTheme: darkTheme, // Your dark ThemeData
      themeMode: ThemeMode.system, // Choose light, dark, or follow system setting
      // ...
    );
  3. Access the Theme: Use Theme.of(context) within your widgets to access the currently active theme data. This ensures your custom widgets automatically adapt to the user’s selected theme (light or dark).
    Color primaryColor = Theme.of(context).colorScheme.primary;
    TextStyle bodyStyle = Theme.of(context).textTheme.bodyMedium;
    
  4. Override Locally: To apply a different theme to a specific part of your widget tree, wrap that section in a Theme widget and pass a new or modified ThemeData instance to its data property. You can use .copyWith() on the parent theme data to only override specific attributes.

✨ Modern Themes and Designs (Material Design 3)

The most modern and recommended approach in Flutter is to embrace Material Design 3 (M3), often referred to as “Material You.” Key design aspects of modern Flutter apps built on M3 include:

  • Dynamic Color: Use ColorScheme.fromSeed() to generate an entire color scheme based on a single brand color (the “seed”). This palette includes light and dark variations with accessible contrast.
  • Emphasis on ColorScheme: Modern theming primarily relies on the M3 ColorScheme roles (like primary, secondary, surface, onPrimary, etc.) rather than deprecated properties like primaryColor and accentColor.
  • Larger, Expressive Typography: The M3 TextTheme uses a more refined set of styles (like displayLarge, headlineMedium, titleSmall) and generally favors larger, more open typefaces for titles and headers.
  • Rounded Shapes: Most M3 components, like buttons, cards, and dialogs, feature more rounded corners than the previous Material Design versions, adding a friendly, organic feel.
  • Elevation and Surface Color: Components use different surface colors and subtle shadows (elevation) to create visual hierarchy, which adapts distinctively in dark mode.

For real-world modern Flutter design examples, you can look to apps that follow Google’s current design language, such as Google Pay, Google Classroom, or showcase projects featured on the Flutter website. For visual inspiration, designer communities often share modern Flutter UI concepts.

The video below demonstrates how to use the ThemeData widget to apply a theme.

This video provides a basic introduction to the ThemeData widget, which is fundamental to implementing theming in Flutter. https://www.youtube.com/watch?v=TkNG9I8g6iY

A second read

You could also implement theming using an InheritedWidget or a ThemeExtension (the modern, preferred way).

1. The Modern Way: ThemeExtension (Recommended)

ThemeExtension is the most modern and clean way to add custom, strongly-typed properties to your existing ThemeData. This allows your custom values to be part of the standard theming system, supporting light/dark mode and context resolution.

Step 1: Define the Extension Class

Create a class that extends ThemeExtension<T> and includes all your custom, contextually parameterized values. It must implement the required copyWith and lerp methods for smooth transitions.

import 'package:flutter/material.dart';

@immutable
class CustomAppColors extends ThemeExtension<CustomAppColors> {
  const CustomAppColors({
    required this.brandColor,
    required this.warningIconColor,
  });

  final Color brandColor;
  final Color warningIconColor;

  // Required: Create a copy of the extension with optional new values
  @override
  CustomAppColors copyWith({
    Color? brandColor,
    Color? warningIconColor,
  }) {
    return CustomAppColors(
      brandColor: brandColor ?? this.brandColor,
      warningIconColor: warningIconColor ?? this.warningIconColor,
    );
  }

  // Required: Interpolate (transition) between two theme extensions
  @override
  CustomAppColors lerp(
    covariant ThemeExtension<CustomAppColors>? other,
    double t,
  ) {
    if (other is! CustomAppColors) {
      return this;
    }
    return CustomAppColors(
      brandColor: Color.lerp(brandColor, other.brandColor, t)!,
      warningIconColor: Color.lerp(warningIconColor, other.warningIconColor, t)!,
    );
  }
}

Step 2: Add the Extension to ThemeData

Apply the custom extension to your light and dark themes using the extensions property in ThemeData.

final lightTheme = ThemeData(
  useMaterial3: true,
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
  extensions: const <ThemeExtension<dynamic>>[
    CustomAppColors(
      brandColor: Colors.purple, // Light mode value
      warningIconColor: Colors.orange,
    ),
  ],
);

final darkTheme = ThemeData(
  useMaterial3: true,
  brightness: Brightness.dark,
  colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark),
  extensions: const <ThemeExtension<dynamic>>[
    CustomAppColors(
      brandColor: Colors.lightBlueAccent, // Dark mode value
      warningIconColor: Colors.redAccent,
    ),
  ],
);

Step 3: Read Values from Context

Access your custom values anywhere in your app using Theme.of(context).extension<T>()!.

class MyCustomWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 1. Retrieve the extension from the current theme (which is determined by context)
    final customColors = Theme.of(context).extension<CustomAppColors>()!;

    return Container(
      // 2. Use the contextually parameterized value
      color: customColors.brandColor, 
      child: Icon(
        Icons.warning,
        color: customColors.warningIconColor,
      ),
    );
  }
}

2. The Traditional Way: InheritedWidget

For simpler, more isolated contextual data that doesn’t need to integrate with ThemeData‘s light/dark switching or animation (lerp), you can use an InheritedWidget (or a state management solution that uses it, like Provider or Riverpod).

  1. Define the Widget: Create a custom InheritedWidget to hold your data.
  2. Wrap the Tree: Wrap the part of the widget tree that needs access to the values with this widget.
  3. Read the Values: Use context.dependOnInheritedWidgetOfExactType<T>() in any descendant widget to retrieve the values based on its location in the tree.

Key Difference: ThemeExtension makes your custom values global to the MaterialApp theme, while a manually placed InheritedWidget can be used to provide local, overridden, contextual parameters to a subtree.

  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels
  • The Bottleneck Factory: AI Production Line vs. Human Quality Gate
  • Most Important Things You Could Code in 2026

Categories

  • Apps (25)
  • ChatGPT (26)
  • Choosing a Framework (38)
  • Flutter (280)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)

Tags

Algorithms (9) crypto (29) flutterdev (39) General (86) Java (7) QR & Bar Codes (3) Software Dev Choices (33) Spring Boot (1) standards (1) Theme (3) User Authentication & Authorization (9) User Experience (10) Utilities (19) WordPress (11)

Product categories

  • All Technologies (87)
    • Flutter Apps (26)
    • GPT (4)
    • Java (39)
    • Native Android (3)
    • PHP (9)
    • Spring (Boot) / Quarkus (36)
    • Utils (15)
    • Vaadin 24+ (28)
    • Vaadin 8 (1)
  • Apps (18)
    • Employees DB (1)
    • Notes (6)
    • Personal Budget (1)
    • Recipes Book (1)
    • Stuff Organizer (1)
    • To-Do (2)
  • PDF Books (3)
  • Source Code Generators (8)

Recent Posts

  • Skipping AI? You’re a Relic – Time to Evolve or Perish!
  • 2026 Flutter Launch Blueprint: Your 10-Step Checklist to App Store Domination
  • Product Requirements Document – for different software development levels
  • The Bottleneck Factory: AI Production Line vs. Human Quality Gate
  • Most Important Things You Could Code in 2026

Post Categories

  • Apps (25)
  • ChatGPT (26)
  • Choosing a Framework (38)
  • Flutter (280)
  • Graphical User Interface (14)
  • Marketing (119)
  • Software Development (292)
  • Spring (45)
  • StartUp (22)
  • Uncategorized (14)
  • Uncategorized (4)
  • Vaadin (16)