Like Google Drive, can I create custom menu in Flutter Web application?.
-
1There is an [open GitHub issue](https://github.com/flutter/flutter/issues/31955) to add that. So I don't think there's an easy way right now. – Lambda Fairy Jun 07 '20 at 11:30
-
1`GestureDetector`'s `onSecondaryTapUp` event works as long as `document.onContextMenu.listen((event) => event.preventDefault());` is run. – nathanfranke Feb 03 '22 at 22:10
5 Answers
Below the instruction how to implement working context menu called via mouse right button in flutter web app:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:universal_html/html.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
// Prevent default event handler
document.onContextMenu.listen((event) => event.preventDefault());
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Center(
child: Listener(
child: Icon(
Icons.ac_unit,
size: 48.0,
),
onPointerDown: _onPointerDown,
),
),
);
}
/// Callback when mouse clicked on `Listener` wrapped widget.
Future<void> _onPointerDown(PointerDownEvent event) async {
// Check if right mouse button clicked
if (event.kind == PointerDeviceKind.mouse &&
event.buttons == kSecondaryMouseButton) {
final overlay =
Overlay.of(context).context.findRenderObject() as RenderBox;
final menuItem = await showMenu<int>(
context: context,
items: [
PopupMenuItem(child: Text('Copy'), value: 1),
PopupMenuItem(child: Text('Cut'), value: 2),
],
position: RelativeRect.fromSize(
event.position & Size(48.0, 48.0), overlay.size));
// Check if menu item clicked
switch (menuItem) {
case 1:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Copy clicked'),
behavior: SnackBarBehavior.floating,
));
break;
case 2:
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Cut clicked'),
behavior: SnackBarBehavior.floating));
break;
default:
}
}
}
}
The only thing is to do is correct positioning of left top corner of context menu.

- 6,126
- 5
- 35
- 51
-
-
But with this solution, we'll surely face problem while changing the screen size of the browser. The menu will be not be at it's correct position. How can we avoid that ? – Mayur Agarwal May 20 '21 at 15:56
-
@MayurAgarwal, did you test? I replaced `Center` with `Container(align: Alignment.bottomRight)` and it looks fine. – BambinoUA May 25 '21 at 11:01
Prevent default contextmenu
Add an oncontextmenu attribute to <html>
tag in web/index.html
:
<!DOCTYPE html>
<html oncontextmenu="event.preventDefault();">
<head>
...
See also: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#event_handler_attributes
This has the same effect as https://stackoverflow.com/a/64779321/16613821 (window.document
is just the <html>
tag), but without triggering "Avoid using web-only libraries outside Flutter web plugin packages." warning or using universal_html
package.
NOTE: Hot reload won't work for this kind of change, but you can simply refresh(F5) browser.
Add your custom contextmenu
https://github.com/flutter/flutter/pull/74286 doesn't work well for your usecase
This should show up by default on desktop, but only when right clicking on EditableText-based widgets. Right clicking elsewhere does nothing, for now.
This is also purposely not customizable or reusable for now. It was a temporary solution that we plan to expand on.
In general, you can use GestureDetector.onSecondaryTap
to detect user's right click.

- 449
- 3
- 11
Until the open issue is resolved, you can do the following in your main()
:
import 'dart:html';
void main() {
window.document.onContextMenu.listen((evt) => evt.preventDefault());
// ...
}

- 41
- 2
-
1Thanks. Now, I could disable right click. How can I customize with custom menu on right click? – Subair K Nov 11 '20 at 03:29
Here is the open issue for it: https://github.com/flutter/flutter/issues/31955
You can disable it for a webpage like this: How do I disable right click on my web page?
You can also listen for Pointer Signal events and render the popup in Flutter: https://medium.com/@crizantlai/flutter-handling-mouse-events-241108731537
Basically on web for example you would disable the default context menu, and show an Overlay in flutter when you receive the right click pointer signal.

- 1,825
- 13
- 20
-
You can use this class: https://api.flutter.dev/flutter/gestures/PointerSignalEvent-class.html And use this widget Listener to have the callback onPointerSignal: https://api.flutter.dev/flutter/widgets/Listener-class.html – Rody Davis Sep 29 '20 at 20:54
-
Thanks doe the info, could u pls send a small example in which something gets printed if the use right clicks? – Maadhav Sharma Sep 30 '20 at 01:00
Thanks for the inspiration BambinoUA. I decided to make my own cross platform class for this.
Works on iOS/Android/Web/Windows/Mac & Linux. Tested.
import 'package:bap/components/splash_effect.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:universal_html/html.dart' as html;
class CrossPlatformClick extends StatefulWidget {
final Widget child;
/**
* Normal touch, tap, right click for platforms.
*/
final Function()? onNormalTap;
/**
* A list of menu items for right click or long press.
*/
final List<PopupMenuEntry<String>>? menuItems;
final Function(String? itemValue)? onMenuItemTapped;
const CrossPlatformClick({Key? key, required this.child, this.menuItems, this.onNormalTap, this.onMenuItemTapped}) : super(key: key);
@override
State<CrossPlatformClick> createState() => _CrossPlatformClickState();
}
class _CrossPlatformClickState extends State<CrossPlatformClick> {
/**
* We record this so that we can use long-press and location.
*/
PointerDownEvent? _lastEvent;
@override
Widget build(BuildContext context) {
final listener = Listener(
child: widget.child,
onPointerDown: (event) => _onPointerDown(context, event),
);
return SplashEffect(
isDisabled: widget.onNormalTap == null,
borderRadius: BorderRadius.zero,
onTap: widget.onNormalTap!,
child: listener,
onLongPress: () {
if (_lastEvent != null) {
_openMenu(context, _lastEvent!);
return;
}
if (kDebugMode) {
print("Last event was null, cannot open menu");
}
},
);
}
@override
void initState() {
super.initState();
html.document.onContextMenu.listen((event) => event.preventDefault());
}
/// Callback when mouse clicked on `Listener` wrapped widget.
Future<void> _onPointerDown(BuildContext context, PointerDownEvent event) async {
_lastEvent = event;
if (widget.menuItems == null) {
return;
}
// Check if right mouse button clicked
if (event.kind == PointerDeviceKind.mouse && event.buttons == kSecondaryMouseButton) {
return await _openMenu(context, event);
}
}
_openMenu(BuildContext context, PointerDownEvent event) async {
final overlay = Overlay.of(context)!.context.findRenderObject() as RenderBox;
final menuItem = await showMenu<String>(
context: context,
items: widget.menuItems ?? [],
position: RelativeRect.fromSize(event.position & Size(48.0, 48.0), overlay.size),
);
widget.onMenuItemTapped!(menuItem);
}
}
The class for standard splash effect touches
import 'package:flutter/material.dart';
class SplashEffect extends StatelessWidget {
final Widget child;
final Function() onTap;
final Function()? onLongPress;
final BorderRadius? borderRadius;
final bool isDisabled;
const SplashEffect({
Key? key,
required this.child,
required this.onTap,
this.isDisabled = false,
this.onLongPress,
this.borderRadius = const BorderRadius.all(Radius.circular(6)),
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (isDisabled) {
return child;
}
return Material(
type: MaterialType.transparency,
child: InkWell(
borderRadius: borderRadius,
child: child,
onTap: onTap,
onLongPress: onLongPress,
),
);
}
}
And how to use it:
return CrossPlatformClick(
onNormalTap: onTapped,
menuItems: [
PopupMenuItem(child: Text('Copy Name', style: TextStyle(fontSize: 16)), value: "copied"),
],
onMenuItemTapped: (item) {
print("item tapped: " + (item ?? "-no-item"));
},
child:

- 7,012
- 5
- 61
- 95