theme_debug_overlay 1.0.2 copy "theme_debug_overlay: ^1.0.2" to clipboard
theme_debug_overlay: ^1.0.2 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 #

pub.dev License: MIT flutter_lints Platform Give Gursha

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 SizedBox in 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 with persistPosition: false).
  • Smooth animationsAnimatedSize for the expanding panel, AnimatedContainer for button state, AnimatedRotation on 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 ThemeMode and a callback; plug it into any state solution.
  • Customisable — configure initialPosition, storageKey, and persistPosition as needed.

Getting started #

1. Add the dependency #

# pubspec.yaml
dependencies:
  theme_debug_overlay: ^1.0.0
flutter pub get

2. Wrap your screen #

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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feat/amazing-feature)
  3. Run the tests (flutter test)
  4. Open a pull request

Support #

If this package saves you time, consider giving a Gursha!

Give Gursha

License #

MIT © 2026 Habesha

1
likes
155
points
70
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A draggable, animated Flutter debug overlay for toggling ThemeMode at runtime. Renders only in debug builds — zero overhead in release.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, shared_preferences

More

Packages that depend on theme_debug_overlay