A beautiful, customizable onboarding/tour guide kit for Flutter apps. Highlight widgets, display tooltips, and guide your users step by step โ perfect for tutorials and product tours.

| Feature | Description |
|---|---|
| ๐ฏ Spotlight Overlay | Highlight any widget with a dark overlay cutout |
| โจ 8 Animation Types | fade, slideUp/Down/Left/Right, scale, bounce, rotate |
| ๐ต 4 Highlight Shapes | Circle, rectangle, pill, rounded rectangle |
| ๐ซ Pulse Effect | Animated pulse ring around highlighted widget |
| ๐ Progress Indicators | Dots, text (โStep 2 of 5โ), or compact (โ2/5โ) |
| โฎ๏ธ Full Navigation | Previous, Next, and Skip buttons |
| ๐จ Customizable | Colors, icons, styles, custom content per step |
| ๐ Smart Positioning | Tooltip auto-positions to avoid edges |
| โจ๏ธ Keyboard Support | Arrow keys, ESC, Enter/Space navigation |
| ๐ Callbacks | onComplete, onSkip, onStepChange events |
| โฑ๏ธ Auto-advance | Configurable timing per step |
| ๐ RTL Support | Works with right-to-left languages |
Add to your pubspec.yaml:
dependencies:
flutter_welcome_kit: ^2.0.0
Then run:
flutter pub get
import 'package:flutter_welcome_kit/flutter_welcome_kit.dart';
class MyApp extends StatefulWidget {
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// 1. Create GlobalKeys for widgets to highlight
final addButtonKey = GlobalKey();
final searchKey = GlobalKey();
final profileKey = GlobalKey();
late TourController _tourController;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// 2. Create the tour controller
_tourController = TourController(
context: context,
steps: [
TourStep(
key: addButtonKey,
title: 'โ Create New',
description: 'Tap here to add a new item.',
backgroundColor: Colors.blue,
animation: StepAnimation.fadeSlideUp,
showPulse: true,
),
TourStep(
key: searchKey,
title: '๐ Search',
description: 'Find anything instantly.',
backgroundColor: Colors.orange,
highlightShape: HighlightShape.circle,
),
TourStep(
key: profileKey,
title: '๐ค Your Profile',
description: 'Manage your account settings.',
backgroundColor: Colors.purple,
isLast: true,
buttonLabel: 'Get Started!',
),
],
onComplete: () => print('Tour finished!'),
onSkip: () => print('Tour skipped'),
);
// 3. Start the tour
_tourController.start();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
key: searchKey, // Attach the key
icon: Icon(Icons.search),
onPressed: () {},
),
IconButton(
key: profileKey, // Attach the key
icon: Icon(Icons.person),
onPressed: () {},
),
],
),
floatingActionButton: FloatingActionButton(
key: addButtonKey, // Attach the key
onPressed: () {},
child: Icon(Icons.add),
),
);
}
}
Configuration for a single tour step.
| Parameter | Type | Default | Description |
|---|---|---|---|
key |
GlobalKey |
required | Widget to highlight |
title |
String |
required | Tooltip title |
description |
String |
required | Tooltip description |
backgroundColor |
Color |
Colors.white |
Tooltip background |
duration |
Duration |
4 seconds |
Auto-advance delay |
buttonLabel |
String? |
"Next"/"Done" |
Button text |
isLast |
bool |
false |
Is this the final step? |
animation |
StepAnimation |
fadeSlideUp |
Entry animation |
highlightShape |
HighlightShape |
rounded |
Spotlight shape |
showPulse |
bool |
false |
Show pulse animation |
customContent |
Widget? |
null |
Replace description with widget |
showProgress |
bool |
true |
Show progress indicator |
progressStyle |
ProgressIndicatorStyle |
dots |
Progress style |
showPreviousButton |
bool |
true |
Show back button |
showSkipButton |
bool |
true |
Show close/skip button |
spotlightPadding |
double |
8.0 |
Padding around highlight |
spotlightBorderRadius |
double |
12.0 |
Border radius (rounded shape) |
allowTargetTap |
bool |
false |
Tap widget to advance |
preferredPosition |
TooltipPosition |
auto |
Force tooltip position |
titleStyle |
TextStyle? |
null |
Custom title style |
descriptionStyle |
TextStyle? |
null |
Custom description style |
icon |
IconData? |
null |
Icon before title |
iconColor |
Color? |
null |
Icon color |
onDisplay |
VoidCallback? |
null |
Callback fired when step is shown |
Manages the tour lifecycle.
TourController(
context: context,
steps: steps,
// Callbacks
onComplete: () {}, // Tour finished
onSkip: () {}, // Tour skipped
onStepChange: (index, step) {}, // Step changed
// Configuration
startDelay: Duration(milliseconds: 500),
overlayColor: Colors.black.withValues(alpha: 0.7),
dismissOnBarrierTap: false,
);
| Method | Description |
|---|---|
start() |
Start tour from step 0 |
startFrom(int index) |
Start from specific step |
next() |
Go to next step |
previous() |
Go to previous step |
goToStep(int index) |
Jump to specific step |
skip() |
Skip/end the tour |
end() |
Alias for skip |
| Property | Type | Description |
|---|---|---|
currentStepIndex |
int |
Current step (0-based) |
totalSteps |
int |
Total number of steps |
isRunning |
bool |
Is tour currently active |
currentStep |
TourStep? |
Current step object |
StepAnimation.fadeSlideUp // Fade + slide from bottom
StepAnimation.fadeSlideDown // Fade + slide from top
StepAnimation.fadeSlideLeft // Fade + slide from right
StepAnimation.fadeSlideRight // Fade + slide from left
StepAnimation.scale // Scale up from center
StepAnimation.bounce // Elastic bounce
StepAnimation.rotate // Rotate while fading
StepAnimation.fade // Simple fade in
StepAnimation.none // No animation
HighlightShape.rounded // Rounded rectangle (default)
HighlightShape.circle // Perfect circle
HighlightShape.pill // Capsule/pill shape
HighlightShape.rectangle // Sharp corners
TooltipPosition.auto // Automatic (default)
TooltipPosition.top // Force above target
TooltipPosition.bottom // Force below target
TooltipPosition.left // Force left of target
TooltipPosition.right // Force right of target
ProgressIndicatorStyle.dots // โโ โ โ (animated dots)
ProgressIndicatorStyle.text // "Step 2 of 5"
ProgressIndicatorStyle.textCompact // "2/5"
ProgressIndicatorStyle.none // No indicator
TourStep(
key: profileKey,
title: 'Complete Your Profile',
description: '',
customContent: Column(
children: [
CircleAvatar(radius: 30, child: Icon(Icons.person)),
SizedBox(height: 12),
Text('Add a photo to personalize your account'),
SizedBox(height: 8),
OutlinedButton(
onPressed: () {},
child: Text('Upload Photo'),
),
],
),
)
// Circle - great for FABs and icons
TourStep(
key: fabKey,
highlightShape: HighlightShape.circle,
spotlightPadding: 4,
...
)
// Pill - great for buttons and chips
TourStep(
key: buttonKey,
highlightShape: HighlightShape.pill,
...
)
// Rounded - great for cards
TourStep(
key: cardKey,
highlightShape: HighlightShape.rounded,
spotlightBorderRadius: 16,
...
)
TourController(
context: context,
steps: steps,
onStepChange: (index, step) {
analytics.logEvent('tour_step', {
'step_index': index,
'step_title': step.title,
});
},
onComplete: () {
analytics.logEvent('tour_completed');
prefs.setBool('has_seen_tour', true);
},
onSkip: () {
analytics.logEvent('tour_skipped');
},
);
Use the onDisplay callback to track when users see specific features โ perfect for progressive disclosure and feature discovery UX patterns:
// Track which features users have discovered
final Set<String> discoveredFeatures = {};
void markFeatureSeen(String featureId) {
discoveredFeatures.add(featureId);
prefs.setStringList('discovered_features', discoveredFeatures.toList());
}
// In your tour steps:
TourStep(
key: inboxKey,
title: 'Inbox',
description: 'You can access incoming messages here.',
onDisplay: () => markFeatureSeen('inbox'),
),
TourStep(
key: settingsKey,
title: 'Settings',
description: 'Customize your app preferences.',
onDisplay: () => markFeatureSeen('settings'),
),
| Key | Action |
|---|---|
Enter / Space |
Next step |
โ / โ |
Previous step |
ESC |
Skip tour |
Make sure the GlobalKey is attached to a widget thatโs currently visible:
// โ
Correct - key is on a visible widget
IconButton(
key: myKey,
icon: Icon(Icons.star),
...
)
// โ Wrong - key is on a widget inside a closed menu
PopupMenuItem(
key: myKey, // Won't work if menu is closed!
...
)
Use startDelay to wait for widgets to render:
TourController(
...
startDelay: Duration(milliseconds: 500),
);
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)MIT License - see LICENSE for details.
Mohammad Usman