flex_overlay 0.6.1+2 copy "flex_overlay: ^0.6.1+2" to clipboard
flex_overlay: ^0.6.1+2 copied to clipboard

A pure positioning system for Flutter popups, tooltips, and overlays. Smart alignment-based positioning with automatic edge detection and fallback strategies, without imposing any styling.

FlexOverlay Logo

flex overlay — pure positioning for Flutter popups & tooltips

Smart positioning system without imposed styling—complete control over appearance

Pub License: MIT Dart Version Platform Support Open Issues Pull Requests Contributors Last Commit


Features #

  • Pure Positioning - No styling imposed, you control 100% of the appearance
  • Smart Alignment - Alignment-based positioning with automatic fallback strategies
  • Dual Interaction Modes - Click or hover interactions
  • Programmatic Control - Show/hide via external state
  • Edge-Aware - Automatically keeps popups within screen bounds
  • Fully Customizable - Gap, offset, edge margins all configurable
  • Dynamic Content - Handles content size changes gracefully
  • Scoped Boundaries - Constrain popups to specific regions
  • Auto-Hide - Optional timeout for automatic dismissal
  • Zero Dependencies - Pure Flutter, no extra packages

What Makes FlexOverlay Special? #

Unlike basic tooltip libraries, FlexOverlay includes intelligent positioning that handles edge cases automatically:

Smart Fallback System Try to show a tooltip on top, but the trigger is near the screen edge? FlexOverlay automatically tries alternative positions:

  1. Preferred position (e.g., top)
  2. Opposite direction (e.g., bottom)
  3. Perpendicular alternatives (e.g., left, right)
  4. Uses the best fit without manual intervention

Overlap Prevention Popups never cover the trigger widget. This is especially important for hover interactions where covering the trigger would cause flickering or break the interaction.

Edge Detection Automatically keeps popups within screen bounds with configurable margins. No popups cut off by screen edges or extending beyond the viewport.

All Without Imposed Styling You get smart positioning while maintaining 100% control over appearance. FlexOverlay handles the "where" so you can focus on the "what."

Demo #

FlexOverlay Example App

Quick Start #

import 'package:flex_overlay/flex_overlay.dart';

Minimal Example #

The simplest tooltip using all defaults:

FlexOverlay(
  content: Container(
    padding: EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: Colors.black87,
      borderRadius: BorderRadius.circular(8),
    ),
    child: Text('Simple tooltip!', style: TextStyle(color: Colors.white)),
  ),
  child: (_) => Text('Hover me'), // Ignore active state if not needed
)

Basic Tooltip with Custom Position #

FlexOverlay(
  positionConfig: PositionConfig.bottom(), // Show below trigger
  content: Container(
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.blue.shade700,
      borderRadius: BorderRadius.circular(12),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(0.2),
          blurRadius: 12,
          offset: Offset(0, 4),
        ),
      ],
    ),
    child: Text('Bottom tooltip', style: TextStyle(color: Colors.white)),
  ),
  child: (isActive) => ElevatedButton(
    onPressed: () {},
    style: ElevatedButton.styleFrom(
      backgroundColor: isActive ? Colors.blue.shade700 : Colors.blue,
    ),
    child: Text('Click me'),
  ),
)

Core Concepts #

1. Interaction Modes #

Click Mode (default)

FlexOverlay(
  interactionConfig: InteractionConfig(mode: InteractionMode.click),
  content: YourPopup(),
  child: (isActive) => YourTrigger(isActive),
)

Hover Mode

FlexOverlay(
  interactionConfig: InteractionConfig(
    mode: InteractionMode.hover,
    hoverShowDelay: Duration(milliseconds: 120),
    hoverHideDelay: Duration(milliseconds: 120),
  ),
  content: YourPopup(),
  child: (isActive) => YourTrigger(isActive),
)

2. Positioning #

Preset Positions

// Top, Bottom, Left, Right
PositionConfig.top()
PositionConfig.bottom()
PositionConfig.left()
PositionConfig.right()

Custom Alignment-Based Positioning

PositionConfig(
  targetAlignment: Alignment.topCenter,    // Point on trigger
  followerAlignment: Alignment.bottomCenter, // Point on popup
  gap: 8.0,                                // Space between trigger and popup
  edgeMargin: 16.0,                        // Minimum margin from screen edges
  offset: Offset(0, 5),                    // Additional manual offset
)

Examples

// Popup above trigger, aligned to left edges
PositionConfig(
  targetAlignment: Alignment.topLeft,
  followerAlignment: Alignment.bottomLeft,
  gap: 8.0,
)

// Popup to the right, aligned at centers
PositionConfig(
  targetAlignment: Alignment.centerRight,
  followerAlignment: Alignment.centerLeft,
  gap: 12.0,
)

3. Programmatic Control #

Control visibility externally using the visible property:

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  bool _showTooltip = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () => setState(() => _showTooltip = !_showTooltip),
          child: Text('Toggle'),
        ),
        FlexOverlay(
          visible: _showTooltip, // External control
          content: YourPopup(),
          child: (_) => YourTrigger(),
        ),
      ],
    );
  }
}

4. Child Builder #

The child builder receives the active state - use it if needed, or ignore it:

With active state styling:

FlexOverlay(
  content: YourPopup(),
  child: (isActive) => Container(
    decoration: BoxDecoration(
      color: isActive ? Colors.blue.shade700 : Colors.blue,
    ),
    child: Text('Styled on active'),
  ),
)

Without active state (just ignore the parameter):

FlexOverlay(
  content: YourPopup(),
  child: (_) => Icon(Icons.info_outline),
)

Advanced Features #

Auto-Hide Timeout #

Automatically hide popup after a duration:

FlexOverlay(
  interactionConfig: InteractionConfig(
    mode: InteractionMode.click,
    autoHideTimeout: Duration(seconds: 3), // Auto-hide after 3s
  ),
  content: YourPopup(),
  child: (isActive) => YourTrigger(isActive),
)

Scoped Boundaries #

Constrain popups to stay within a specific region:

FlexOverlayScope(
  child: Sidebar(
    child: Column(
      children: [
        FlexOverlay(
          // This popup will stay within the sidebar bounds
          content: YourPopup(),
          child: (isActive) => YourTrigger(isActive),
        ),
      ],
    ),
  ),
)

Perfect for:

  • Sidebars
  • Dialogs
  • Panels
  • Scrollable regions
  • Split-pane layouts

Fine-Tuning Position #

FlexOverlay(
  positionConfig: PositionConfig(
    targetAlignment: Alignment.topCenter,
    followerAlignment: Alignment.bottomCenter,
    gap: 12.0,           // Space between trigger and popup
    edgeMargin: 20.0,    // Minimum distance from screen edges
    offset: Offset(5, -3), // Additional manual tweaking
  ),
  content: YourPopup(),
  child: (isActive) => YourTrigger(isActive),
)

Rich Content Popups #

FlexOverlay doesn't impose styling, so you can create any design:

FlexOverlay(
  content: Container(
    constraints: BoxConstraints(maxWidth: 350),
    padding: EdgeInsets.all(20),
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.purple.shade50, Colors.blue.shade50],
      ),
      borderRadius: BorderRadius.circular(16),
      boxShadow: [
        BoxShadow(
          color: Colors.black.withOpacity(0.15),
          blurRadius: 24,
          offset: Offset(0, 12),
        ),
      ],
    ),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Row(
          children: [
            Icon(Icons.lightbulb, color: Colors.amber),
            SizedBox(width: 12),
            Text('Pro Tip', style: TextStyle(fontWeight: FontWeight.bold)),
          ],
        ),
        SizedBox(height: 12),
        Text('You have complete control over styling!'),
        SizedBox(height: 16),
        ElevatedButton(onPressed: () {}, child: Text('Learn More')),
      ],
    ),
  ),
  child: (isActive) => YourTrigger(isActive),
)

Common Use Cases #

Tooltip #

FlexOverlay(
  interactionConfig: InteractionConfig(mode: InteractionMode.hover),
  positionConfig: PositionConfig.top(),
  content: Container(
    padding: EdgeInsets.all(8),
    decoration: BoxDecoration(
      color: Colors.grey.shade800,
      borderRadius: BorderRadius.circular(4),
    ),
    child: Text('Tooltip text', style: TextStyle(color: Colors.white)),
  ),
  child: (_) => Icon(Icons.help_outline),
)
FlexOverlay(
  interactionConfig: InteractionConfig(mode: InteractionMode.click),
  positionConfig: PositionConfig.bottom(),
  content: Container(
    width: 200,
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(8),
      boxShadow: [BoxShadow(blurRadius: 8, color: Colors.black26)],
    ),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        ListTile(title: Text('Option 1'), onTap: () {}),
        ListTile(title: Text('Option 2'), onTap: () {}),
        ListTile(title: Text('Option 3'), onTap: () {}),
      ],
    ),
  ),
  child: (isActive) => TextButton(
    onPressed: () {},
    child: Row(
      children: [
        Text('Menu'),
        Icon(isActive ? Icons.arrow_drop_up : Icons.arrow_drop_down),
      ],
    ),
  ),
)

Context Menu #

FlexOverlay(
  interactionConfig: InteractionConfig(mode: InteractionMode.click),
  positionConfig: PositionConfig(
    targetAlignment: Alignment.bottomRight,
    followerAlignment: Alignment.topRight,
  ),
  content: YourContextMenu(),
  child: (isActive) => IconButton(
    icon: Icon(Icons.more_vert),
    onPressed: () {},
  ),
)

Popover Card #

FlexOverlay(
  interactionConfig: InteractionConfig(mode: InteractionMode.click),
  positionConfig: PositionConfig.bottom(),
  content: Card(
    elevation: 8,
    child: Padding(
      padding: EdgeInsets.all(16),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('User Profile', style: TextStyle(fontWeight: FontWeight.bold)),
          SizedBox(height: 8),
          Text('email@example.com'),
          SizedBox(height: 12),
          ElevatedButton(onPressed: () {}, child: Text('View Profile')),
        ],
      ),
    ),
  ),
  child: (isActive) => CircleAvatar(child: Icon(Icons.person)),
)

API Reference #

FlexOverlay #

Parameter Type Default Description
child Widget Function(bool isActive) required Builder for trigger widget that receives active state. Ignore the parameter if you don't need it.
content Widget required The popup content to display
positionConfig PositionConfig PositionConfig.top() Position configuration
interactionConfig InteractionConfig InteractionConfig() Interaction behavior configuration
visible bool? null Override for programmatic control

PositionConfig #

Parameter Type Default Description
targetAlignment Alignment required Point on trigger widget
followerAlignment Alignment required Point on popup widget
gap double 8.0 Space between trigger and popup
edgeMargin double 16.0 Minimum margin from screen edges
offset Offset? null Additional manual offset

Presets: .top(), .bottom(), .left(), .right()

InteractionConfig #

Parameter Type Default Description
mode InteractionMode InteractionMode.click Click or hover interaction
hoverShowDelay Duration 120ms Delay before showing on hover
hoverHideDelay Duration 120ms Delay before hiding after hover exit
autoHideTimeout Duration? null Auto-hide after duration

FlexOverlayScope #

Parameter Type Description
child Widget Widget tree defining the constrained region

Tips & Best Practices #

  1. Use alignment-based positioning for stable popups that don't jump when content size changes
  2. Set appropriate edge margins to ensure popups don't touch screen edges
  3. Use FlexOverlayScope when working with sidebars, panels, or split layouts
  4. Leverage programmatic control for complex UI flows (wizards, tours, etc.)
  5. Keep hover delays reasonable (100-200ms) for good UX
  6. Style your popups consistently across your app for a cohesive experience

Example App #

Run the example app to see all features in action:

cd example
flutter run

The example demonstrates:

  • All positioning modes
  • Click vs hover interactions
  • Programmatic control
  • Edge constraint handling
  • Dynamic content
  • Fine-tuning parameters
  • Scoped boundaries
  • Rich content styling

License #

MIT License - see LICENSE file for details.

Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

1
likes
140
points
75
downloads

Publisher

verified publishercodealchemist.dev

Weekly Downloads

A pure positioning system for Flutter popups, tooltips, and overlays. Smart alignment-based positioning with automatic edge detection and fallback strategies, without imposing any styling.

Repository (GitHub)
View/report issues

Topics

#overlay #portal #tooltip #popup #positioning

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flex_overlay