flutter_root_context_menu

A customizable context menu package for Flutter with animation support, flexible styling, and web-like behavior.

Features

  • Manual trigger with full control over position and timing
  • Optional menu title (header label) to distinguish menus from different sources
  • Optional barrier that blocks the UI behind an open menu
  • Nested submenu support with unlimited depth
  • 9 built-in animations + custom animation support
  • Custom widget items (Switch, Checkbox, Slider, etc.)
  • Customizable styling (colors, sizes, elevation, shadows, border radius)
  • Smart positioning with automatic overflow prevention
  • Area constraints via ContextMenuArea
  • Auto-close on route changes via ContextMenuRouteObserver
  • Async callback support

Installation

dependencies:
  flutter_root_context_menu: ^0.11.0

Quick Start

import 'package:flutter_root_context_menu/flutter_root_context_menu.dart';

GestureDetector(
  onSecondaryTapDown: (details) {
    showRootContextMenu(
      context: context,
      position: details.globalPosition,
      items: [
        ContextMenuItem(
          label: 'Copy',
          icon: Icon(Icons.copy, size: 18),
          onTap: () => print('Copy'),
        ),
        ContextMenuItem.divider(),
        ContextMenuItem(
          label: 'Delete',
          icon: Icon(Icons.delete, size: 18),
          textColor: Colors.red,
          onTap: () => print('Delete'),
        ),
      ],
    );
  },
  child: Text('Right-click me'),
)

Show a header label at the top of the root menu — useful when multiple widgets share similar menus and you want the user to know what they right-clicked.

showRootContextMenu(
  context: context,
  position: details.globalPosition,
  title: 'File Options',
  items: [
    ContextMenuItem(label: 'Open', onTap: () {}),
    ContextMenuItem(label: 'Rename', onTap: () {}),
    ContextMenuItem(label: 'Delete', onTap: () {}),
  ],
);

Customize via ContextMenuConfig.titleStyle and ContextMenuConfig.titlePadding. Title is rendered only on the root menu (not submenus) and is followed by a divider.

Barrier

By default, widgets behind an open menu keep reacting to the pointer — hover effects, cursors and scrolling still work, and a click outside closes the menu and also reaches the widget underneath. Pass useBarrier: true to block pointer input (hover, clicks, scrolling) outside the menu while it is open:

showRootContextMenu(
  context: context,
  position: details.globalPosition,
  useBarrier: true,
  items: [...],
);

With the barrier on, a click outside still closes the menu, but that click is consumed: it does not activate the widget below, and a right-click elsewhere closes the menu without opening a new one. The barrier is invisible, does not affect keyboard input, and is removed together with the menu.

To make a right-click elsewhere close the menu and open the next one in a single click (like native desktop apps), also pass barrierPassesSecondaryClick: true:

showRootContextMenu(
  context: context,
  position: details.globalPosition,
  useBarrier: true,
  barrierPassesSecondaryClick: true,
  items: [...],
);

Only secondary clicks pass through — hover, primary clicks and scrolling stay blocked. Any widget below that handles secondary taps receives the click, not just context-menu areas. Has no effect without useBarrier.

Type Description
ContextMenuItem(...) Standard item with label, icon, onTap
ContextMenuItem.divider() Separator line
ContextMenuItem.custom(builder: ...) Custom widget (Switch, Checkbox, etc.)
ContextMenuItem.submenu(...) Nested submenu with children
ContextMenuItem.submenu(
  label: 'Share',
  icon: Icon(Icons.share, size: 18),
  children: [
    ContextMenuItem(label: 'Email', onTap: () {}),
    ContextMenuItem(label: 'Slack', onTap: () {}),
    ContextMenuItem.divider(),
    ContextMenuItem.submenu(
      label: 'Social',
      children: [
        ContextMenuItem(label: 'Twitter', onTap: () {}),
        ContextMenuItem(label: 'Facebook', onTap: () {}),
      ],
    ),
  ],
)

Submenus support:

  • Unlimited nesting depth (configurable via SubmenuConfig.maxDepth)
  • Auto left/right flip when hitting screen edges
  • Hover open/close with configurable delays
  • Tap to open on touch devices
  • Independent animation and duration settings
  • All item types inside submenus (standard, divider, custom, submenu)

Configuration Options

ContextMenuConfig(
  // Style
  backgroundColor: Colors.white,
  hoverColor: Color(0xFFE0E0E0),
  textStyle: TextStyle(fontSize: 14),
  disabledTextStyle: null,             // null = textStyle with Colors.grey
  itemHeight: 40.0,
  minWidth: 180.0,
  maxWidth: 280.0,
  itemPadding: EdgeInsets.symmetric(horizontal: 16.0),
  borderRadius: BorderRadius.circular(8.0),
  elevation: 8.0,
  boxShadow: null,

  // Layout
  screenPadding: EdgeInsets.zero,
  itemBorderRadius: BorderRadius.zero,
  itemMargin: EdgeInsets.zero,
  dividerMargin: EdgeInsets.zero,
  menuPadding: EdgeInsets.zero,
  iconWidth: 0,
  iconSpacing: 0,

  // Title (header label, shown when `title` is passed to showRootContextMenu)
  titleStyle: TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.w600,
    color: Color(0xFF757575),
  ),
  titlePadding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),

  // Animation
  animationDuration: Duration(milliseconds: 200),
  animationBuilder: ContextMenuAnimations.popup,

  // Submenu
  submenu: SubmenuConfig(
    openDelay: Duration(milliseconds: 200),
    closeDelay: Duration(milliseconds: 300),
    horizontalOffset: -4.0,
    animationBuilder: null,            // null = auto (slideRight/slideLeft)
    animationDuration: null,           // null = use animationDuration
    maxDepth: 5,
    icon: null,                        // null = default chevron_right
  ),
)

Available Animations

Animation Description
popup Scale + fade (default)
fade Fade in only
slideUp Slide from bottom
slideDown Slide from top
slideRight Slide from left
slideLeft Slide from right
bounce Elastic bounce effect
scale Scale only
none No animation

Helper Functions

closeRootContextMenu();       // Close the menu
isRootContextMenuOpen();      // Check if menu is open

Documentation

Example App

cd example
flutter run

License

MIT License