Reaction Overlay
A floating reaction bar for Flutter, inspired by WhatsApp and iMessage. It anchors to any widget and provides quick reactions, an emoji picker, adaptive positioning, accessibility, and light/dark theme support.
Installation
dependencies:
reaction_overlay: ^0.1.0
import 'package:reaction_overlay/reaction_overlay.dart';
Quick start
Wrap a message, or any widget, with ReactionTarget. Long press it to open the reaction bar.
ReactionTarget(
reactions: [
Reaction('👍', label: 'Like', onTap: () => saveReaction('👍')),
Reaction('❤️', label: 'Love', onTap: () => saveReaction('❤️')),
Reaction('😂', label: 'Funny', onTap: () => saveReaction('😂')),
],
child: const MessageBubble(text: 'Hello'),
)
ReactionTarget does not require a GlobalKey: it uses the widget's BuildContext to calculate its position.
WhatsApp-style double tap and full bar
Set quickReactionIndex to react immediately on double tap. Long press still opens the full reaction picker.
ReactionTarget(
reactions: ReactionSet.standard.reactions,
quickReactionIndex: 0, // 👍
onReactionChanged: (index, reaction) {
saveReaction(reaction.emoji!);
},
child: MessageBubble(text: message.text),
)
If you do not set quickReactionIndex, TriggerMode.doubleTap opens the full reaction bar instead.
Centralized configuration
Use ReactionOverlayConfig when you reuse the same configuration or want to keep ReactionOverlay.show calls readable.
final messageReactions = ReactionOverlayConfig(
reactions: ReactionSet.social.reactions,
position: OverlayPosition.auto,
maxVisibleButtons: 6,
dimBackground: true,
showExpandButton: true,
recentEmojis: const ['👍', '❤️', '🙏'],
enableDragPreview: true,
enableSelectionParticles: false,
);
ReactionOverlay.show(
context: context,
config: messageReactions,
onReactionChanged: (index, reaction) {
saveReaction(reaction.emoji!);
},
onDismissed: () => debugPrint('Reaction picker closed'),
);
Do not mix config with individual styling parameters in ReactionOverlay.show; use the centralized configuration as the source of truth for those values. Callbacks passed to the method (onDismissed, onReactionChanged, and onEmojiSelected) can be supplied normally.
Common configuration options
| Option | Default | Purpose |
|---|---|---|
reactions |
required | The list of Reaction objects shown in the bar. |
position |
OverlayPosition.auto |
Position: auto, above, below, left, or right. |
maxVisibleButtons |
6 |
Maximum before horizontal navigation appears. |
showExpandButton |
true |
Opens the full emoji panel. |
recentEmojis |
[] |
Emojis shown first in the full panel. |
dimBackground / dimColor |
true / 20% black |
Controls the background behind the bar. |
enableDragPreview |
true |
Magnifies the reaction under the finger while dragging. |
enableSelectionParticles |
true |
Shows particles on selection; disable for a quieter style. |
allowMultipleSelections |
false |
Keeps the picker open after a selection. |
Buttons keep a minimum 48×48 px tap target even if buttonSize or buttonHeight is smaller. The package also respects the system's reduce-motion preference.
Show the overlay manually
Use ReactionOverlay.show when you provide your own gesture. The context must belong to a widget that has already been laid out.
GestureDetector(
onTap: () {
ReactionOverlay.show(
context: context,
reactions: [Reaction('👍'), Reaction('❤️')],
position: OverlayPosition.above,
onReactionChanged: (index, reaction) => saveReaction(reaction.emoji!),
);
},
child: const MessageBubble(text: 'Tap me'),
)
Custom reactions and presets
final reactions = [
Reaction('👍', label: 'Like'),
Reaction.custom(
child: const Icon(Icons.star, color: Colors.amber),
label: 'Favorite',
),
];
// Presets: standard, social, work, gaming, and minimal.
final socialReactions = ReactionSet.social.reactions;
Global theme
Configure visual values that apply to all ReactionTarget widgets.
MaterialApp(
theme: ThemeData(
extensions: const [
ReactionOverlayThemeData(
position: OverlayPosition.above,
enableHapticFeedback: true,
buttonSize: 44,
buttonHeight: 48,
),
],
),
home: const ChatPage(),
)
Properties passed directly to ReactionTarget take precedence over this theme.
Programmatic control
ReactionOverlayController lets you inspect visibility and the most recently selected reaction.
final controller = ReactionOverlayController();
controller.show(
context: context,
reactions: ReactionSet.standard.reactions,
);
// controller.isVisible
// controller.selectedReaction
controller.dismiss();
// Call dispose when the controller is no longer needed.
controller.dispose();
Migrating from 0.0.3
Version 0.1.0 simplifies the API and removes manual key management.
| In 0.0.3 | In 0.1.0 |
|---|---|
AppReactionOverlayManager |
ReactionOverlay.show, ReactionTarget, or ReactionOverlayController. |
itemKey / itemId |
Remove these parameters; pass the BuildContext to ReactionOverlay.show or use ReactionTarget. |
List<IconButton> buttons |
List<Reaction> reactions. |
| Manually building the overlay | ReactionTarget for the usual case. |
1. Replace IconButton with Reaction
Previously, reactions were built with IconButton widgets. Use the simpler model now:
// 0.0.3
final buttons = [
IconButton(icon: const Icon(Icons.thumb_up), onPressed: onLike),
];
// 0.1.0
final reactions = [
Reaction('👍', label: 'Like', onTap: onLike),
];
To keep a custom icon, use Reaction.custom:
Reaction.custom(
child: const Icon(Icons.thumb_up),
label: 'Like',
onTap: onLike,
)
2. Remove GlobalKey, itemKey, and itemId
Instead of managing position through a key, wrap the content:
// 0.1.0
ReactionTarget(
reactions: reactions,
child: MessageBubble(text: message.text),
)
If you need to open the picker from your own gesture, call ReactionOverlay.show with that widget's context, as shown above.
3. Replace the manager
Replace AppReactionOverlayManager with one of these options:
ReactionTarget: recommended declarative integration.ReactionOverlay.show: when you already have a gesture callback.ReactionOverlayController: when another part of your app needs to close, toggle, or observe the overlay.
Accessibility and interaction
- Long press opens the reaction bar; quick double tap is optional.
- Left/right arrows navigate, Enter/Space selects, and Escape closes.
- Give every
Reactionalabelso screen readers announce a useful action. - Use
onReactionChangedto synchronize a selection with your backend.
Demo
See the example directory for a demo application.