flutter_anchor 0.0.3 copy "flutter_anchor: ^0.0.3" to clipboard
flutter_anchor: ^0.0.3 copied to clipboard

A Flutter package for dynamic anchored overlays, including tooltips, popovers, and more

Anchor #

License: MIT

A package for building highly-customizable overlay UIs. flutter_anchor makes it easy to create tooltips, popovers, menus, and more. Its core positioning engine is inspired by the powerful JavaScript library Floating UI.


Usage #

1. Simple Tooltip (AnchorTooltip) #

The easiest way to get started is to wrap your widget with AnchorTooltip.

AnchorTooltip(
  message: const Text(
    'This is a simple tooltip',
    style: TextStyle(color: Colors.white),
  ),
  backgroundColor: Colors.grey[800],
  arrowShape: const RoundedArrow(),
  child: const Icon(Icons.info),
)

2. Styled Popover (AnchorPopover) #

For more complex content, use AnchorPopover. It provides a styled container with an arrow, border, and background color.

AnchorPopover(
  // Show the popover on tap
  triggerMode: const AnchorTriggerMode.tap(),
  placement: Placement.bottom,
  overlayWidth: 250,
  // Use a built-in arrow shape
  arrowShape: const RoundedArrow(),
  backgroundColor: Colors.white,
  border: BorderSide(color: Colors.grey[300]!),
  
  // Build your custom overlay content
  overlayBuilder: (context) => Padding(
    padding: const EdgeInsets.all(12),
    child: Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        const Text('This is a styled popover!'),
        ElevatedButton(
          onPressed: () {},
          child: const Text('A Button'),
        ),
      ],
    ),
  ),
  
  // The widget that triggers the popover
  child: ElevatedButton(
    child: const Text('Tap Me'),
    onPressed: () {},
  ),
)

3. Full Control (Anchor) #

The base Anchor widget gives you full control over triggers and appearance.

Use AnchorTriggerMode.focus to show an overlay when a TextField is focused.

final _focusNode = FocusNode();

Anchor(
  triggerMode: AnchorTriggerMode.focus(focusNode: _focusNode),
  placement: Placement.bottom,
  // The overlay doesn't have any default styling
  overlayBuilder: (context) {
    return Container(
      width: 300,
      height: 200,
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border.all(color: Colors.grey.shade300),
        borderRadius: BorderRadius.circular(8),
      ),
      child: ListView(
        children: const [
          ListTile(title: Text('Suggestion 1')),
          ListTile(title: Text('Suggestion 2')),
        ],
      ),
    );
  },
  child: TextField(
    focusNode: _focusNode,
    decoration: const InputDecoration(hintText: 'Search...'),
  ),
)

Manual Trigger (e.g., Menu Bar)

Use AnchorTriggerMode.manual and an AnchorController to show/hide overlays programmatically.

final _controller = AnchorController();

Anchor(
  controller: _controller,
  triggerMode: const AnchorTriggerMode.manual(),
  placement: Placement.bottomStart,
  overlayBuilder: (context) => _buildMenuContent(),
  child: IconButton(
    icon: const Icon(Icons.menu),
    // Toggle the menu on button press
    onPressed: () => _controller.toggle(),
  ),
)

4. Context Menu (AnchorContextMenu) #

Use AnchorContextMenu to show a menu at a specific screen coordinate, such as a cursor position.

// 1. Wrap your main content area
AnchorContextMenu(
  menuBuilder: (context) {
    // 3. Build your menu
    return Material(
      elevation: 8,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            title: const Text('Copy'),
            onTap: () => context.hideMenu(),
          ),
          ListTile(
            title: const Text('Paste'),
            onTap: () => context.hideMenu(),
          ),
        ],
      ),
    );
  },
  // 2. Add a gesture detector to show the menu
  childBuilder: (context) => GestureDetector(
    onSecondaryTapDown: (event) {
      // Show the menu at the global cursor position
      context.showMenu(event.globalPosition);
    },
    onLongPressStart: (details) {
      // Also show for long-press on mobile
      context.showMenu(details.globalPosition);
    },
    child: Container(
      color: Colors.grey[100],
      child: const Center(child: Text('Right-click or long-press me')),
    ),
  ),
)

Advanced Customization #

Positioning Middleware #

While AnchorTooltip, AnchorPopover, and Anchor handle common cases with built-in positioning logic, you can customize how overlays are positioned using the AnchorMiddlewares widget.

This system allows you to compose a list of behaviors that run in order to compute the final position of the overlay. You can implement custom middleware by extending the PositioningMiddleware class.

final _controller = AnchorController();

// Wrap your RawAnchor with AnchorMiddlewares to define custom positioning
AnchorMiddlewares(
  middlewares: const [
    // 1. Add a 10px gap between the child and overlay
    OffsetMiddleware(mainAxis: 10),

    // 2. If it overflows, try the opposite side
    FlipMiddleware(preferredDirection: AxisDirection.up),

    // 3. If it still overflows (e.g., on the sides), shift it
    ShiftMiddleware(preferredDirection: AxisDirection.up),
  ],
  child: RawAnchor(
    // A controller to manage the overlay state
    controller: _controller,

    // This is the initial, preferred placement
    placement: Placement.top,

    overlayBuilder: (context) {
      return Container(
        padding: const EdgeInsets.all(8),
        color: Colors.black,
        child: const Text('My Overlay', style: TextStyle(color: Colors.white)),
      );
    },
    child: ElevatedButton(
      onPressed: () {},
      child: const Text('Tap Me'),
    ),
  ),
)

Backdrop #

You can create a backdrop (like a modal barrier or blur filter) by providing a backdropBuilder.

AnchorPopover(
  // ... other properties
  backdropBuilder: (context) => ClipPath(
    // Clip a "hole" where the child widget is
    clipper: _BackdropClipper(
      exclude: AnchorData.of(context).geometry.childBounds,
      excludeRadius: BorderRadius.circular(8),
    ),
    // Apply a blur filter to the entire backdrop
    child: BackdropFilter(
      filter: ImageFilter.blur(sigmaX: 4, sigmaY: 4),
      child: Container(color: Colors.black26),
    ),
  ),
  child: YourGridItem(),
)

// See the `grid_demo.dart` file for the `_BackdropClipper` implementation.

Demos #

This package can be used to build a wide variety of common UI patterns.

Demo Description
Wikipedia Links Hover over links to show a rich preview card, just like on Wikipedia.
Search Autocomplete Display a list of suggestions when a TextField gains focus.
Chat Reactions Show an emoji reaction bar on hover, just like in MS Teams or Slack.
Desktop UI Build macOS-style menu bars (manual click) and dock tooltips (hover).
Grid Popovers Tap grid items to show a detailed popover with a beautiful blur backdrop.
Context Menus Open a menu at the cursor's position on right-click or long-press.

Contributing #

Contributions are welcome! Feel free to open issues or submit pull requests on GitHub.

2
likes
0
points
223
downloads

Publisher

verified publisherdegenk.com

Weekly Downloads

A Flutter package for dynamic anchored overlays, including tooltips, popovers, and more

Repository (GitHub)
View/report issues

Topics

#tooltip #popover #context-menu #overlay

License

unknown (license)

Dependencies

anchor, flutter, meta

More

Packages that depend on flutter_anchor