hint_kit 1.3.0 copy "hint_kit: ^1.3.0" to clipboard
hint_kit: ^1.3.0 copied to clipboard

Tooltips, persistent hints and spotlight guided tours from one overlay engine, one placement resolver and one theme. Zero runtime dependencies.

hint_kit #

pub package pub points license: MIT

Flutter toolkit for tooltips, persistent hints, and guided tours using one overlay engine, placement resolver, theme, and controller pattern.

It supports disabled widgets, route-spanning tours, real spotlight passthrough, reusable themes, animations, persistence, localization, analytics, and accessibility β€” with zero runtime dependencies.

Screenshots #

Hints, designs and animations A four-step guided tour
A hint on a disabled button, three of the ready-made designs, the transitions, a hand-drawn caret and a show-once callout A tour: the spotlight travels between targets, step 3 expands a panel first, and step 4 waits for another route

Both recorded on a device from example/, captioned as they go. The tour is the one worth watching: the spotlight travels between targets rather than cutting, step 3 expands a panel before it appears, and step 4 waits until you push the route it lives on.

A hint on a disabled button The branded preset The glass preset
A dark tooltip pointing at a disabled Check in button A tinted bubble using the branded preset A translucent bubble over a blurred background
A tour step, spotlight and all A retinted, lighter scrim Dark mode, unconfigured
A passthrough tour step with a hole cut in the scrim The same tour over a navy scrim at 55%, with the controls that set it The same screen in dark mode with an inverted bubble

Every one of these is the example app on a device β€” nothing is mocked up. Run it yourself: cd example && flutter run.

Features #

  • 🚫 Disabled widgets β€” show hints even when the child is disabled.
  • πŸ—ΊοΈ Guided tours β€” ordered steps that can continue across routes.
  • πŸ”¦ Spotlight β€” real scrim hole with optional hit-test passthrough.
  • 🎨 10 presets β€” material, minimal, soft, contrast, branded, sharp, card, glass, cupertino, adaptive.
  • ✨ Animations β€” scale, fade, pop, slide, none, or custom.
  • πŸ’Ύ Persistence β€” show-once hints and interrupted-tour resume.
  • 🌍 Localization β€” custom labels and progress formatting.
  • πŸ“Š Analytics β€” app-wide hint and tour observer.
  • β™Ώ Accessibility β€” bubbles spoken as live regions, focus handling, text scaling, high contrast, reduced motion, and Esc support.
  • πŸ“¦ Zero dependencies β€” pure Flutter.

Install #

dependencies:
  hint_kit: ^1.3.0

or:

flutter pub add hint_kit
import 'package:hint_kit/hint_kit.dart';

Quick start #

Hint #

Hint(
  message: 'You need an active shift to check in',
  child: ElevatedButton(
    onPressed: null,
    child: const Text('Check in'),
  ),
);

Hint supports tap, longPress, hover, focus, manual, onAppear, and secondaryTap triggers.

Controller #

final controller = HintController();

Hint(
  controller: controller,
  triggers: const {HintTrigger.manual},
  message: 'Saved successfully',
  child: saveButton,
);

controller.show();
controller.hide();
controller.toggle();

Guided tours #

Wrap the application with TourScope and mark the steps with HintTarget:

TourScope(
  child: MaterialApp(
    home: HintTarget(
      tour: 'onboarding',
      order: 1,
      title: 'Start here',
      description: 'Tap this button to continue.',
      child: const Text('Check in'),
    ),
  ),
);

Start the tour with:

Tour.read(context).start('onboarding');

Control a tour with:

tour.next();
tour.previous();
tour.skip();
tour.finish();
tour.cancel();

Tours can wait for targets that are not currently mounted, making route-spanning onboarding possible.

Conditional steps #

A step that is simply not built is one the tour waits for β€” it cannot tell "this user does not have that feature" from "this user has not opened that screen yet". Say so explicitly:

HintTarget(
  tour: 'onboarding',
  order: 3,
  enabled: user.canApproveShifts,
  child: approveButton,
)

The child renders either way; only the tour ignores it. An opted-out step is subtracted from tourLengths, so the card reads "3 of 4" rather than "3 of 5" with one that never comes.

For the cases you cannot predict β€” a target deleted in a refactor, a screen that fails to load β€” give the scope a deadline:

TourScope(
  stepTimeout: const Duration(seconds: 5),
  onStepUnavailable: (tour, index) => analytics.log('tour_step_missing', index),
  child: const MyApp(),
);

It is null by default, deliberately: waiting forever is what makes route-spanning tours work.

Spotlight & passthrough #

HintTarget(
  tour: 'onboarding',
  order: 1,
  spotlight: SpotlightShape.circle,
  passthrough: true,
  child: checkInButton,
)

With passthrough: true, interaction inside the spotlight can reach the actual target.

Show once #

Display a hint only once:

Hint(
  showOnce: 'payslip-tip',
  triggers: const {HintTrigger.onAppear},
  message: 'Payslips live here now',
  child: payslipTab,
);

For persistent storage, assign your own TourStorage implementation:

TourScope(
  storage: myStorage,
  child: const MyApp(),
);

HintRegistry.instance.storage = myStorage;

Both wires are needed, and they are separate on purpose: TourScope.storage persists tour progress, HintRegistry.instance.storage persists showOnce hints. Setting one and not the other warns in debug.

Theming #

Configure a shared theme with HintThemeData:

MaterialApp(
  theme: ThemeData(
    extensions: const [
      HintThemeData(
        preset: HintPreset.branded,
        maxWidth: 320,
      ),
    ],
  ),
);

Or configure an individual hint:

Hint(
  theme: const HintThemeData(
    preset: HintPreset.glass,
  ),
  message: 'This is a glass-style hint',
  child: button,
);

Themes control colors, borders, radius, elevation, padding, arrow, placement spacing, typography, transitions, blur, scrim, and spotlight settings.

Rich bubbles #

Use contentBuilder for custom interactive content:

Hint(
  interactive: true,
  triggers: const {HintTrigger.hover, HintTrigger.tap},
  contentBuilder: (context) => Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      const Text('Payslip #4821'),
      TextButton(
        onPressed: _download,
        child: const Text('Download'),
      ),
    ],
  ),
  child: chip,
);

Beacon #

For a lightweight alternative to a full tour:

Beacon(
  title: 'Duplicate a shift',
  message: 'Long-press a shift to copy it.',
  pulseCount: 3,
  child: const Icon(Icons.calendar_month),
);

API surface #

Type Purpose
Hint Tooltip / hint widget
HintController Programmatic hint control
HintTarget Guided-tour step
TourScope Tour configuration and storage
TourController Start and control tours
HintThemeData Shared and per-widget styling
HintPreset Ready-made designs
HintQueue Sequential hints without a tour
Beacon Pulsing hint indicator
HintObserver Analytics / lifecycle events
TourStorage Persistence for tours and show-once hints
resolvePlacement Pure placement resolver

Requirements #

  • Flutter 3.24+
  • Dart 3.5+
  • Android, iOS, web, macOS, Windows, Linux

Notes #

  • Hint requires an Overlay ancestor, normally provided by MaterialApp, CupertinoApp, or Navigator.
  • An ancestor IgnorePointer or AbsorbPointer can prevent the hint from receiving pointer events.
  • Lazy off-screen list items cannot be targeted until they are built.
  • The default InMemoryTourStorage does not persist between app launches.
  • A Beacon with unlimited pulsing can keep widget tests from settling; use pulseCount or disable auto-start.

License #

MIT

11
likes
160
points
334
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Tooltips, persistent hints and spotlight guided tours from one overlay engine, one placement resolver and one theme. Zero runtime dependencies.

Repository (GitHub)
View/report issues

Topics

#tooltip #onboarding #overlay #showcase #ui

License

MIT (license)

Dependencies

flutter

More

Packages that depend on hint_kit