Popup-Menu (Context Menu) in Flutter

Introduction
Popup menu is a popular graphical user interface control element for displaying additional options to the user. The menu that displays when you click the right-button of the mouse to show extra options such as rename, delete, copy, and paste to the user is the most typical example of a popup menu. (This is also known as the context-menu.)
Ok, popup menus have a great utility in apps, but how to create them in Flutter. We can use PopupMenuButton and PopupMenuItem widgets to create them.
PopupMenuButton
It is the widget which is responsible for creating a popup-menu. A menu has many items, each item is created by a different widget — the PopupMenuItem widget.
PupupMenuButton has various properties (parameters of the constructor) which are described in the below sections.
itemBuilder
It is a function prop, hence takes a callback function which creates popup-menu. itemBuilder will supply widgets to display inside PopupMenuButton items using the PopupMenuItem widget. A popup-menu can have many items (options), hence itemBuilder must return a list of PopupMenuItem.
import 'package:flutter/material.dart';void main() {
runApp(const MyApp());
}class MyApp extends StatelessWidget {
const MyApp({super.key}); // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
primarySwatch: Colors.blue,
),
home: const AppHome(),
);
}
}class AppHome extends StatelessWidget {
const AppHome({Key? key}) : super(key: key); @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Popup Menu",)),
body: PopupMenuButton( // build the popup menu items
itemBuilder: (BuildContext context){
return const [
PopupMenuItem(
value: 1,
child: Text("Create"),
), PopupMenuItem(
value: 2,
child: Text("Remove"),
), PopupMenuItem(
value: 3,
child: Text("Update"),
), PopupMenuItem(
value: 4,
child: Text("Delete"),
),
];
},
), );
}
}The above code creates a popup menu which is represented by the three vertical dots button. When it is tapped/clicked, the popup menu will be displayed containing the options (menu items) defined by the itemBuilder function prop.
itemBuilder returns a list of popup menu items, each popup menu item is defined by a PopupMenuItem widget. Hence, itemBuilder returns a list of PopupMenuItem widgets.
The PopupMenuItem widget has various props to define each menu item. Here, I have used two props — child and value.
The child property inside a PopupMenuItem can be any widget. Here, I have set it to a Text widget, but it can be any Flutter widget. For example to have an icon together with text, Text and Icon widgets can be used as I will explain in the later section.
PopupMenuItem also contains a value prop (parameter in constructor) that is used to assign a value to each popup menu item. This value identifies a popup menu item within the popup-menu. This value is received within the function prop.
The value can be of different types, here I have used integer values, string values can also be used.
The below preview video shows the popup-menu.

initialValue
When the popup menu is launched, the value you assigned to the initialValue prop, and the corresponding menu item is highlighted. To use it, you must use value prop inside the PopupMenuItem.
The initialValue will highlight the item that has same value; with a grey background when showing the popup-menu.
Add the prop initialValue:3, within the PopupMenuButton to highlight the menu-item “update” since it corresponds to the value 3.
This is shown in the below preview image .

onSelected
This is a function prop, hence takes a callback function. This callback function receives the value of the selected (tapped/pressed) menu-item from the popup menu as its parameter.
Add the below code to use this function prop to the PopupMenuButton
onSelected: (value){
SnackBar snackBar = SnackBar(
content: Text("You have selected the menu item: $value", textScaleFactor: 2,),
duration: const Duration(seconds: 5),
); // show he snack bar (message)
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},When a menu option is selected by the user, the popup-menu will disappear and this function is called which receives the value of the selected menu item which identifies which menu-item is selected by the user.
We can initiate any kind of processing (a sequence of actions) upon the menu item selection, however in above code I have just shown a message that displays which menu-item is selected by the user. In real production apps, there could be more complex processing like fetching, updating database etc.
The message is displayed using the SnackBar and ScaffoldMessenger widgets.
The preview after the addition of the above code is as follows:

onCanceled
Like the onSelected, this is also a function prop.
The specified callback function is invoked when the user taps/clicks the area outside the popup-menu i.e. the user dismisses the popup menu without selecting an item.
Tapping/Clicking outside the popup menu will let the popup menu goes away.
Add the below lines of code to the PopupMenuButton to use this function prop.
onCanceled: (){ SnackBar snackBar = const SnackBar(
content: Text("You have cancelled (not selected) the menu item:",
style: TextStyle(fontSize: 20),), duration: Duration(seconds: 5), backgroundColor: Colors.teal,
showCloseIcon: true,
closeIconColor: Colors.red, ); // show he snack bar (message)
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},Like the onSelected, here also any action can be initiated. For demonstration, I have shown a message. However, unlike the SnackBar of onSelected, this time background-color is specified and a close button is also added to the snack-bar message.
The preview video below shows the effect of this prop.

onOpened
This is also a function prop which is called when the popup-menu is opened displayed upon tapping/pressing the three-vertical-dots (popup-menu-button)
I will stick to displaying a message but any kind of processing can be dobe within this function.
Add the below lines of code to learn this prop of PopupMenuButton widget.
onOpened: (){
SnackBar snackBar = const SnackBar(
content: Text("Popup menu is opened- select an option.",
style: TextStyle(fontSize: 20),), duration: Duration(seconds: 5),
backgroundColor: Colors.indigoAccent, ); ScaffoldMessenger.of(context).showSnackBar(snackBar);
},I have also wrapped the entire PopupMenuButton inside the Center widget to show the three-vertical-dots at the Scaffold center.
The effect of the above changes is shown in the below preview video.

iconSize
This property determines the size of the three-vertical-dots icon.
Add the line iconSize: 100, to the PopupMenuButton for this.
The preview is as follows.

Adding icons to the menu item
Since PopupMenuItem widget has the child prop which can take only a single Flutter widget, we have to use multi-children widget such as Row widget to have more than text in menu items
The below modified code of the itemBuilder prop creates menu-items having both — icon and label using a Row widget.
Please noe that the const keyword before the list is now removed due to use of Row widget constructor. It is now added to the list within the children prop of Row widget. However, the use of the const keyword is optional and will affect the output. It is used for performance purposes only.
itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
value: 1,
child: Row( children: const [Icon(Icons.create), Text("Create")],),
),
PopupMenuItem(
value: 2,
child:Row( children: const [Icon(Icons.remove_circle_outline), Text("Remove")],),
),
PopupMenuItem(
value: 3,
child:Row( children: const [Icon(Icons.update ), Text("Update")],),
),
PopupMenuItem(
value: 4,
child:Row(children: const [Icon(Icons.delete), Text("Delete")],),
),
];
},The preview of the above code is as follows:

Oh! You are not satisfied by the spacing between the icon and the label in menu-items.
It can be adjusted using Row props
mainAxisAlignment: MainAxisAlignment.spaceBetween
but a better way to do so is to use widgets like SizedBox.
Below is the code for one menu-item which uses SizedBox widget to add some space between icon and the label to enhance look.
PopupMenuItem(
value: 4,
child:Row( children: const [Icon(Icons.delete), SizedBox(width: 10,), Text("Delete")],),
),The preview is as follows after adding a gap.

You can also use style prop of Text widget to enhance the label.
Customizing Popup-Menu
You can customize the popup-menu using props such as tooltip, elevation, padding, offset etc. Let’s explore each of them in turn below.
tooltip
A brief description of the popup-menu which will be displayed when the user long-presses the popup-menu-button (the three-vertical-dots). It takes a String.
Add a line tooltip: “The operations on data-base”, for this.
The preview is as shown in the below video.

elevation:
This prop decides the size of the shadow behind the popup-menu. Add a line elevation: 200, within the PopupMenuButton for this.

position:
This prop decides whether the popup menu sits above or below the popup menu button.
If this parameter is not set, PopupMenuThemeData.position is used instead. If PopupMenuThemeData.position is likewise null, the position will be PopupMenuPosition.over, which will cause the popup menu to display immediately over the button (three vertical-dots) that was used to generate it .
Thir prop takes two values — PopupMenuPosition.over and PopupMenuPosition.under.
1 — PopupMenuPosition.over
Add the line position: PopupMenuPosition.over to the popup-menu-button.
The all above preview images and videos were produced with this position: PopupMenuPosition.over.
2 — PopupMenuPosition.under
This value produces the output like below.

offset:
Useful property when you want to display a menu at a particular position. This prop is used to move the popup menu relative to the position specified by the position prop.
Specifying this prop like offset: Offset(0,100), will produce the below output.

offset prop comes handy when popup-menu-button is placed inside another widget like app-bar, to position it appropriately.
color
Provides the background color for the Popup Menu.
Adding the line color: Colors.redAccent, to the PopupMenuButton will produce the following output.

shape
Used to provide a specific shape to the popup menu.
The below code for the shape prop produces the following output.
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50),
),
Each corner can be given a different degree of circularity as shown in the below code for the shape prop.
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(50),
topRight: Radius.circular(30),
bottomRight: Radius.circular(15),
bottomLeft: Radius.circular(20),
)),The preview of the popup-menu is shown below after the above code for shape prop.

Changing the popup-menu-button
The child prop of PopupMenuButton can be used to change the button (three-vertical-dots in above preview images/videos) which is used to generate the Popup-Menu. The child prop takes a Flutter widget.
Specify the child prop as follows:
child: const Text( "I am hero", style: TextStyle(fontSize: 20),),The preview video after this addition is as follows:

change the child widget as follows:
child: const Icon(Icons.settings_applications_outlined, size: 100,),Then, the output will be like below.

The complete code until this point is shown in the code-block.
import 'package:flutter/material.dart';void main() {
runApp(const MyApp());
}class MyApp extends StatelessWidget {
const MyApp({super.key}); // This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
primarySwatch: Colors.blue,
),
home: const AppHome(),
);
}
}class AppHome extends StatelessWidget {
const AppHome({Key? key}) : super (key: key); @override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(
"Popup Menu",
)),
body: Center(
child: PopupMenuButton(
initialValue: 3, iconSize: 100, tooltip: "The operations on data-base", elevation: 200,position: PopupMenuPosition.under,
offset: Offset(0, 100), color: Colors.redAccent,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(50),
topRight: Radius.circular(30),
bottomRight: Radius.circular(15),
bottomLeft: Radius.circular(20),
)), // this prop takes a widget which replace
// the default three-vertical dots button of popup-menu-button
// child:const Text("I am hero", style: TextStyle(fontSize: 20),), child: const Icon(Icons.settings_applications_outlined, size: 100,), onOpened: () {
SnackBar snackBar = const SnackBar(
content: Text(
"Popup menu is opened- select an option.",
style: TextStyle(fontSize: 20),
),
duration: Duration (seconds: 5),
backgroundColor: Colors.indigoAccent,
); ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, onSelected: (value) {
SnackBar snackBar = SnackBar(
content: Text(
"You have selected the menu item: $value",
textScaleFactor: 2,
),
duration: const Duration(seconds: 5),
); // show he snack bar (message)
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, onCanceled: () {
SnackBar snackBar = const SnackBar(
content: Text(
"You have cancelled (not selected) the menu item:",
style: TextStyle(fontSize: 20),
),
duration: Duration(seconds: 5),
backgroundColor: Colors.teal,
showCloseIcon: true,
closeIconColor: Colors.red,
); // show he snack bar (message)
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, // build the popup menu items
itemBuilder: (BuildContext context) {
return [
PopupMenuItem(
value: 1,
child: Row(
children: const [
Icon(Icons.create),
SizedBox(
width: 10,
),
Text("Create")
],
),
),
PopupMenuItem(
value: 2,
child: Row(
children: const [
Icon(Icons.remove_circle_outline),
SizedBox(
width: 10,
),
Text("Remove")
],
),
),
PopupMenuItem(
value: 3,
child: Row(
children: const [
Icon(Icons.update),
SizedBox(
width: 10,
),
Text("Update")
],
),
),
PopupMenuItem(
value: 4,
child: Row(
children: const [
Icon(Icons.delete),
SizedBox(
width: 10,
),
Text("Delete")
],
),
),
];
},
),
),
);
}
}A realistic placement of Popup-menu
In a real (production) app, the popup-menu is not placed inside some panel, bar etc. The below code places the above designed popup-menu in the app-bar (action bar). For this, I will use the actions prop of AppBar widget.
AppBar.actions
This property of AppBar takes a list of widgets since a widget represents an action. The widgets list denotes actions which are placed in an action bar or tool-bar. The toolbar (of actions) is displayed just after the title in the app bar.
The action bar isaligned to the right of AppBar.
Although it is a list of widgets, here we will use only PopupMenuButton widget (i.e. list has a sinlge widget — PopupMenuButton ) to create the popup-menu.
Updated code snippet is as follows
Please note that, offset property is set like offset: const Offset(0, 110), to position the popup-menu just below the app-bar. Also the icon-size within child property is set to fit the icon within the app-bar.
A Text widget is now placed as the Sacffold body content since PopupMenuButton is moved to the app-bar.
The preview is shown in the below video.

Recapitulate
This article talks about how to create a popup menu and customize the appearance of it.
With that we have reached the end of this article. Thank you once again for reading. If you liked this, don’t forget to 👏 and follow 😍. Also subscribe to our weekly newsletter at https://www.devtechie.com






