theme_debug_overlay 1.0.3
theme_debug_overlay: ^1.0.3 copied to clipboard
A draggable, animated Flutter debug overlay for toggling ThemeMode at runtime. Renders only in debug builds — zero overhead in release.
theme_debug_overlay #
A draggable, animated debug overlay that lets you toggle ThemeMode
(light / dark / system) at runtime — without touching a single line of
app code.
Zero overhead in production. The overlay renders an empty
SizedBoxin profile and release builds (kDebugMode == false), so shipping it unconditionally is completely safe.
Features #
- Three-mode switcher — Light, Dark, System — with animated selection highlighting.
- Fully draggable — long-press and drag to any corner of the screen.
- Position persistence — the last position is restored across app restarts
via
SharedPreferences(opt-out withpersistPosition: false). - Smooth animations —
AnimatedSizefor the expanding panel,AnimatedContainerfor button state,AnimatedRotationon the main icon. - Tooltip support — every button carries a tooltip for accessibility and hover UX.
- Framework agnostic — no Riverpod, Bloc, or Provider required. Pass your
current
ThemeModeand a callback; plug it into any state solution. - Customisable — configure
initialPosition,storageKey, andpersistPositionas needed.
Getting started #
1. Add the dependency #
# pubspec.yaml
dependencies:
theme_debug_overlay: ^1.0.0
flutter pub get
2. Wrap your screen #
Option A — ThemeDebugOverlayScaffold (recommended)
import 'package:theme_debug_overlay/theme_debug_overlay.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ThemeMode _themeMode = ThemeMode.system;
@override
Widget build(BuildContext context) {
return MaterialApp(
themeMode: _themeMode,
theme: ThemeData.light(useMaterial3: true),
darkTheme: ThemeData.dark(useMaterial3: true),
home: ThemeDebugOverlayScaffold(
themeMode: _themeMode,
onThemeModeChanged: (mode) => setState(() => _themeMode = mode),
child: const HomeScreen(),
),
);
}
}
Option B — manual Stack placement
Stack(
children: [
const HomeScreen(),
ThemeDebugOverlay(
themeMode: _themeMode,
onThemeModeChanged: (mode) => setState(() => _themeMode = mode),
),
],
)
Usage #
With Riverpod #
// In your widget
final themeMode = ref.watch(themeModeProvider);
ThemeDebugOverlayScaffold(
themeMode: themeMode,
onThemeModeChanged: (mode) =>
ref.read(themeModeProvider.notifier).set(mode),
child: const HomeScreen(),
)
With Bloc / Cubit #
BlocBuilder<ThemeCubit, ThemeMode>(
builder: (context, themeMode) => ThemeDebugOverlayScaffold(
themeMode: themeMode,
onThemeModeChanged: context.read<ThemeCubit>().setThemeMode,
child: const HomeScreen(),
),
)
With Provider / ChangeNotifier #
Consumer<ThemeNotifier>(
builder: (context, notifier, _) => ThemeDebugOverlayScaffold(
themeMode: notifier.themeMode,
onThemeModeChanged: notifier.setThemeMode,
child: const HomeScreen(),
),
)
Custom initial position #
ThemeDebugOverlay(
themeMode: _themeMode,
onThemeModeChanged: _handleThemeChange,
initialPosition: const Offset(16, 300), // bottom-left area
)
Disable persistence #
ThemeDebugOverlay(
themeMode: _themeMode,
onThemeModeChanged: _handleThemeChange,
persistPosition: false, // position resets each launch
)
Multiple overlays (unique storage keys) #
ThemeDebugOverlay(
themeMode: _themeMode,
onThemeModeChanged: _handleThemeChange,
storageKey: 'overlay_screen_a',
)
API reference #
ThemeDebugOverlay #
| Parameter | Type | Default | Description |
|---|---|---|---|
themeMode |
ThemeMode |
required | The currently active theme mode. |
onThemeModeChanged |
ValueChanged<ThemeMode> |
required | Called when the user picks a new mode. |
persistPosition |
bool |
true |
Save/restore position with SharedPreferences. |
initialPosition |
Offset |
Offset(16, 100) |
Starting position (used when no persisted value exists). |
storageKey |
String |
'theme_debug_overlay' |
SharedPreferences key prefix. Change for multiple overlays. |
ThemeDebugOverlayScaffold #
All parameters from ThemeDebugOverlay, plus:
| Parameter | Type | Default | Description |
|---|---|---|---|
child |
Widget |
required | The widget displayed beneath the overlay. |
How it works #
ThemeDebugOverlay
└─ Positioned (left: _x, top: _y — updated on drag end)
└─ ScaleTransition (entrance animation)
└─ Draggable (long-press to drag)
└─ Column
├─ AnimatedSize
│ └─ [Light / Dark / System option buttons]
└─ Main toggle button (opens/closes the panel)
Position saves are debounced by 500 ms, so a single drag results in at
most one SharedPreferences write after the user stops moving the overlay.
Platform support #
| Platform | Supported |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| Web | ✅ |
| macOS | ✅ |
| Windows | ✅ |
| Linux | ✅ |
Contributing #
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feat/amazing-feature) - Run the tests (
flutter test) - Open a pull request
Support #
If this package saves you time, consider giving a Gursha!
License #
MIT © 2026 Habesha