Skip to main content

Implementing Dark Mode

1. Using ThemeMode

Property Values Description
ThemeMode.system ThemeMode.system Uses the system's dark/light mode setting.
ThemeMode.light ThemeMode.light Forces the app to use light mode.
ThemeMode.dark ThemeMode.dark Forces the app to use dark mode.

Example: Setting ThemeMode in MaterialApp

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      themeMode: ThemeMode.system, // or ThemeMode.light, ThemeMode.dark
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      home: Scaffold(
        appBar: AppBar(title: Text("Dark Mode Demo")),
        body: Center(child: Text("Hello, World!")),
      ),
    );
  }
}

2. Defining Dark Theme

Property Value Description
brightness Brightness.dark Defines the theme's brightness level
primaryColor Colors.blue Primary color used for app bars, buttons, etc.
scaffoldBackgroundColor Colors.grey[900] Background color for the main screen
cardColor Colors.grey[800] Color for cards and containers
textTheme TextTheme.dark() Defines text styles for dark mode

Example: Defining a Dark Theme

final ThemeData darkTheme = ThemeData(
  brightness: Brightness.dark,
  primaryColor: Colors.blue,
  scaffoldBackgroundColor: Colors.grey[900],
  cardColor: Colors.grey[800],
  textTheme: TextTheme.dark(),
);

3. Detecting System Theme

Property Description Values
ThemeMode.system Uses the system's theme setting ThemeMode.system
ThemeMode.light Forces light theme ThemeMode.light
ThemeMode.dark Forces dark theme ThemeMode.dark

Example: Detecting System Theme

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.light(),
      darkTheme: ThemeData.dark(),
      themeMode: ThemeMode.system, // Detects system theme
      home: Scaffold(
        appBar: AppBar(title: Text("Theme Demo")),
        body: Center(
          child: Text("System theme detected"),
        ),
      ),
    );
  }
}
Note: ThemeMode.system will automatically switch between light and dark themes based on the user's system settings.

4. Switching Themes Dynamically

Property Description Example
ThemeMode Defines the current theme mode (light, dark, system) ThemeMode.dark
ChangeNotifier Used to notify UI when theme changes ChangeNotifier()

Example: Theme Switcher Widget

class ThemeChanger extends ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.system;

  ThemeMode get themeMode => _themeMode;

  void setTheme(ThemeMode theme) {
    _themeMode = theme;
    notifyListeners();
  }
}

class ThemeSwitcher extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<ThemeChanger>(
      builder: (context, themeChanger, child) {
        return MaterialApp(
          themeMode: themeChanger.themeMode,
          theme: ThemeData.light(),
          darkTheme: ThemeData.dark(),
        );
      },
    );
  }
}

Example: Toggle Theme Button

IconButton(
  icon: Icon(Icons.brightness_4),
  onPressed: () {
    final themeChanger = context.read<ThemeChanger>();
    if (themeChanger.themeMode == ThemeMode.light) {
      themeChanger.setTheme(ThemeMode.dark);
    } else {
      themeChanger.setTheme(ThemeMode.light);
    }
  },
)
Note: The ChangeNotifier pattern ensures that UI updates when the theme changes dynamically.

5. Using Brightness Enum

Property Description Values
Brightness.light Represents a light theme Brightness.light
Brightness.dark Represents a dark theme Brightness.dark

Example: Checking Brightness

final brightness = WidgetsBinding.instance.window.platformBrightness;
if (brightness == Brightness.dark) {
  // Apply dark theme
} else {
  // Apply light theme
}

6. Creating Dark Theme ColorScheme

Property Values Description
primary Color(0xFF6200EE) Primary color for the theme
onPrimary Color(0xFFFFFFFF) Color for content on primary color
secondary Color(0xFF03DAC6) Secondary color for the theme
onSecondary Color(0xFF000000) Color for content on secondary color
surface Color(0xFF121212) Background color for surfaces
onSurface Color(0xFFFFFFFF) Color for content on surface
background Color(0xFF121212) Background color for the app
onBackground Color(0xFFFFFFFF) Color for content on background
error Color(0xFFB00020) Error color for the theme
onError Color(0xFFFFFFFF) Color for content on error color

Example: Defining a Dark ColorScheme

final darkColorScheme = ColorScheme(
  primary: Color(0xFF6200EE),
  onPrimary: Color(0xFFFFFFFF),
  secondary: Color(0xFF03DAC6),
  onSecondary: Color(0xFF000000),
  surface: Color(0xFF121212),
  onSurface: Color(0xFFFFFFFF),
  background: Color(0xFF121212),
  onBackground: Color(0xFFFFFFFF),
  error: Color(0xFFB00020),
  onError: Color(0xFFFFFFFF),
);

7. Customizing Widgets for Dark Mode

Widget Dark Mode Properties Notes
Text style: TextStyle(color: Colors.white) Use Colors.white for better contrast in dark mode
Container decoration: BoxDecoration(color: Colors.grey[800]) Set background color to a dark shade like Colors.grey[800]
Card color: Colors.grey[900] Use Colors.grey[900] for a dark card background
AppBar backgroundColor: Colors.grey[900] Set backgroundColor to a dark color for contrast

Example: Customizing Text for Dark Mode

<Text
  text "Hello, Dark Mode!"
  style="TextStyle(
    color: Colors.white,
    fontSize: 18,
    fontWeight: FontWeight.bold
  )"
/>

Example: Customizing Container for Dark Mode

<Container
  decoration: BoxDecoration(
    color: Colors.grey[800],
    borderRadius: BorderRadius.circular(8)
  )
  child: Text(
    "Dark Background",
    style: TextStyle(color: Colors.white)
  )
/>

8. Using Theme Extensions

Property Description Values Notes
ThemeExtension<T> Base class for creating custom theme extensions ThemeExtension<T> Used to extend Flutter's built-in themes with custom properties
copyWith Creates a new instance with specified properties overridden ThemeExtension<T> copyWith({T? property}) Used to create a modified version of the extension
merge Merges this extension with another of the same type ThemeExtension<T> merge(ThemeExtension<T> other) Used to combine two theme extensions
maybeResolve Resolves a theme extension in the context of a given theme ThemeExtension<T>? maybeResolve(ThemeData theme) Used to resolve a theme extension in the context of an existing theme

Example: Creating a Custom Theme Extension

class CustomThemeExtension extends ThemeExtension<CustomThemeExtension> {
  final Color primaryColor;

  CustomThemeExtension({
    required this.primaryColor,
  });

  @override
  ThemeExtension<CustomThemeExtension> copyWith({
    Color? primaryColor,
  }) {
    return CustomThemeExtension(
      primaryColor: primaryColor ?? this.primaryColor,
    );
  }

  @override
  ThemeExtension<CustomThemeExtension> merge(
      ThemeExtension<CustomThemeExtension> other) {
    if (other is CustomThemeExtension) {
      return CustomThemeExtension(
        primaryColor: other.primaryColor,
      );
    }
    return this;
  }

  @override
  ThemeExtension<CustomThemeExtension>? maybeResolve(
      ThemeData theme) {
    return this;
  }
}

Example: Applying a Custom Theme Extension

final customTheme = ThemeData(
  extensions: [
    CustomThemeExtension(primaryColor: Colors.blue),
  ],
);

MaterialApp(
  theme: customTheme,
  home: Scaffold(
    appBar: AppBar(
      title: Text('Custom Theme Example'),
    ),
    body: Center(
      child: Text('Hello, World!'),
    ),
  ),
);
Note: Theme extensions are a powerful way to customize Flutter themes beyond the built-in properties. They allow for more granular control over theme elements and can be used to create complex, reusable themes.

9. Storing Theme Preferences

Example: Saving theme preference to SharedPreferences

import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveThemeMode(ThemeMode themeMode) async {
  final prefs = await SharedPreferences.getInstance();
  prefs.setString('theme_mode', themeMode.toString().split('.').last);
}

ThemeMode loadThemeMode() {
  final prefs = SharedPreferences.getInstance();
  final themeString = prefs.getString('theme_mode') ?? 'system';
  switch (themeString) {
    case 'light':
      return ThemeMode.light;
    case 'dark':
      return ThemeMode.dark;
    default:
      return ThemeMode.system;
  }
}
Preference Key Value Type Description
theme_mode string Stores the selected theme mode: 'light', 'dark', or 'system'
Note: The shared_preferences package is commonly used for simple key-value storage in Flutter apps.
Warning: Avoid storing large objects or complex data structures in shared preferences. Use it only for simple settings like theme mode.

10. Using Material 3

Property Values Description
themeMode ThemeMode.system, ThemeMode.light, ThemeMode.dark Controls the theme mode of the app. Defaults to ThemeMode.system.
useMaterial3 true, false Enables or disables Material 3 (Material Design 3) for the app. Defaults to true.

Example: Configuring Material 3 Theme

MaterialApp(
  themeMode: ThemeMode.system,
  useMaterial3: true,
  theme: ThemeData.light(),
  darkTheme: ThemeData.dark(),
)
Note: Material 3 is the latest design language from Google, offering improved accessibility and a more consistent look across platforms.

Was this article helpful?