auto_theme lets you design your Flutter app in one mode and automatically get
the opposite mode for free.
Build a light theme and it generates the dark theme. Build a dark theme and it generates the light theme.
| Light mode | Dark mode (fallback off) | Dark mode (fallback on) |
|---|---|---|
![]() |
![]() |
![]() |
- Start with a single
ThemeData themeis optional (a default Material 3 theme is used if omitted)- Get both
themeanddarkTheme - Works with Material 3 and
ColorScheme - Includes runtime theme switching
- Ships with a simple
MaterialAppwrapper - Still lets you override the generated opposite theme when needed
- Includes an optional hardcoded-color fallback for non-themed widgets
auto_theme works best when your app already uses Flutter theming properly:
ThemeDataColorScheme- Material component themes like
AppBarTheme,CardTheme, andInputDecorationTheme
If your widgets use lots of hardcoded colors, custom painting, or branded image assets, you can enable a fallback color filter. It is best-effort and may also affect images and brand assets.
dependencies:
auto_theme: ^0.1.1Wrap your app and provide only one theme:
import 'package:auto_theme/auto_theme.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return AutoThemeApp.materialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.indigo,
brightness: Brightness.light,
),
),
home: const HomePage(),
);
}
}You can also omit theme:
AutoThemeApp.materialApp(
home: const HomePage(),
)That single theme becomes:
themewhen the source theme is lightdarkThemewhen the source theme is dark- an auto-generated opposite theme for the other side
Use the builder constructor if you want full control over MaterialApp:
AutoThemeApp(
theme: myDarkTheme,
initialThemeMode: ThemeMode.dark,
builder: (context, lightTheme, darkTheme, themeMode) {
return MaterialApp(
theme: lightTheme,
darkTheme: darkTheme,
themeMode: themeMode,
home: const HomePage(),
);
},
);If you don't want the wrapper widget, generate the missing theme directly:
final darkTheme = ThemeGenerator.generateOpposite(myLightTheme);Access the controller anywhere below AutoThemeApp:
final controller = AutoThemeApp.of(context);
controller.toggle();
controller.setLight();
controller.setDark();
controller.setSystem();Use the included switch in an app bar:
AppBar(
actions: const [
AutoThemeSwitch(includeSystem: true),
],
)Or use the switch-style toggle:
const AutoThemeToggle()If the generated theme gets you 90% of the way there, you can still inject a hand-tuned opposite theme:
AutoThemeApp.materialApp(
theme: myLightTheme,
oppositeTheme: myDarkTheme,
home: const HomePage(),
)If your app has many hardcoded widget colors (Container(color: ...),
TextStyle(color: ...)) you can enable a global fallback filter:
AutoThemeApp.materialApp(
theme: myLightTheme, // optional
hardcodedColorStrategy: HardcodedColorStrategy.colorFilter,
hardcodedColorFilterStrength: 1.0, // 0.0 to 1.0
home: const HomePage(),
)How it behaves:
- When the active mode matches your source mode, no global filter is applied
- When the opposite mode is active, the fallback filter is applied app-wide
- It is best-effort and can also alter photos, logos, and other assets
auto_theme doesn't do a naive RGB inversion. It retunes colors by role:
- Backgrounds become appropriate light or dark surfaces
- Accent colors keep their hue but shift to more usable lightness values
- Text colors are adjusted for readability
- Contrast is checked and corrected where possible
- Common Material theme sections are mapped into the generated theme
A complete demo is included in example/lib/main.dart.
Run it with:
cd example
flutter run -d macos

