Context menus, or text selection toolbars, are the menus that show up when long pressing or right clicking on text in Flutter, and they show options like **Cut**, **Copy**, **Paste**, and **Select all**. Previously, it was only possible to narrowly customize them using `ToolbarOptions` and `TextSelectionControls`. Now, they have been made composable using widgets, just like everything else in Flutter, and the specific configuration parameters have been deprecated.
Original behavior
Previously, it was possible to disable buttons from the context menus using `TextSelectionControls`, but any customization beyond that required copying and editing hundreds of lines of custom classes in the framework. Now, all of this has been replaced by a simple builder function, `contextMenuBuilder`, which allows any Flutter widget to be used as a context menu.
New behavior
Context menus are now built from the `contextMenuBuilder` parameter, which has been added to all text-editing and text-selection widgets. If one is not provided, then Flutter just sets it to a default that builds the correct context menu for the given platform. All of these default widgets are exposed to users for re-use. Customizing context menus now consists of using `contextMenuBuilder` to return whatever widget you want, possibly including reusing the built-in context menu widgets.
- Who is affected
- Applications and packages relying on the previous behaviour or signature. Code that uses ToolbarOptions, TextSelectionControls, TextField and related APIs.
- Practical impact
- Behaviour or API differences that can break builds or change runtime behaviour.
- What action should I take?
- Follow the official migration guide below, update affected call sites, run `dart fix --apply` where a fix is available, then run your tests.
Official migration guide (excerpt, © The Flutter Authors, CC BY 3.0)
In general, any previous changes to context menus that have been deprecated now
require the use of the contextMenuBuilder parameter on the relevant
text-editing or text-selection widget (
on TextField,
for example). Return a built-in context menu widget like
AdaptiveTextSelectionToolbar
to use Flutter's built-in context menus, or return your own widget for something
totally custom.
To transition to contextMenuBuilder, the following parameters and classes have
been deprecated.
ToolbarOptions
This class was previously used to explicitly enable or disable certain buttons
in a context menu. Before this change, you might have passed it into TextField
or other widgets like this:
// Deprecated.
TextField(
toolbarOptions: ToolbarOptions(
copy: true,
),
)Now, you can achieve the same effect by adjusting the buttonItems passed into
AdaptiveTextSelectionToolbar. For example, you could ensure that the Cut
button never appears, but the other buttons do appear as usual:
TextField(
contextMenuBuilder: (context, editableTextState) {
final List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
buttonItems.removeWhere((ContextMenuButtonItem buttonItem) {
return buttonItem.type == ContextMenuButtonType.cut;
});
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
)Or, you could ensure that the Cut button appears exclusively and always:
TextField(
contextMenuBuilder: (context, editableTextState) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: <ContextMenuButtonItem>[
ContextMenuButtonItem(
onPressed: () {
editableTextState.cutSelection(SelectionChangedCause.toolbar);
},
type: ContextMenuButtonType.cut,
),
],
);
},
)TextSelectionControls.canCut and other button booleans
These booleans previously had the same effect of enabling and disabling certain
buttons as ToolbarOptions.cut, and so on had. Before this change, you might
have been hiding and showing buttons by overriding TextSelectionControls and
setting these booleans like this:
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
bool canCut() => false,
}See the previous section on ToolbarOptions for how to achieve a similar effect
with contextMenuBuilder.
TextSelectionControls.handleCut and other button callbacks
These functions allowed the modification of the callback called when the buttons were pressed. Before this change, you might have been modifying context menu button callbacks by overriding these handler methods like this:
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
bool handleCut() {
// My custom cut implementation here.
},
}This is still possible using contextMenuBuilder, including calling
out to the original buttons' actions in the custom handler, using toolbar
widgets like AdaptiveTextSelectionToolbar.buttonItems.
This example shows modifying the Copy button to show a dialog in addition to doing its usual copy logic.
TextField(
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
final List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
final int copyButtonIndex = buttonItems.indexWhere(
(ContextMenuButtonItem buttonItem) {
return buttonItem.type == ContextMenuButtonType.copy;
},
);
if (copyButtonIndex >= 0) {
final ContextMenuButtonItem copyButtonItem =
buttonItems[copyButtonIndex];
buttonItems[copyButtonIndex] = copyButtonItem.copyWith(
onPressed: () {
copyButtonItem.onPressed();
Navigator.of(context).push(
DialogRoute<void>(
context: context,
builder: (BuildContext context) =>
const AlertDialog(
title: Text('Copied, but also showed this dialog.'),
),
);
)
},
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
)A full example of modifying a built-in context menu action can be found in the samples repository in modified_action_page.dart on GitHub.
buildToolbar
This function generated the context menu widget similarly to
contextMenuBuilder, but required more setup to use. Before this change, you
might have been overriding buildToolbar as a part of TextSelectionControls,
like this:
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
Offset lastSecondaryTapDownPosition,
) {
return _MyCustomToolbar();
},
}Now you can simply use contextMenuBuilder directly as a parameter to
TextField (and others). The information provided in the parameters to
buildToolbar can be obtained from the EditableTextState that is passed to
contextMenuBuilder.
The following example shows how to build a fully-custom toolbar from scratch while still using the default buttons.
class _MyContextMenu extends StatelessWidget {
const _MyContextMenu({
required this.anchor,
required this.children,
});
final Offset anchor;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: anchor.dy,
left: anchor.dx,
child: Container(
width: 200,
height: 200,
color: Colors.amberAccent,
child: Column(
children: children,
),
),
),
],
);
}
}
class _MyTextField extends StatelessWidget {
const _MyTextField();
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
maxLines: 4,
minLines: 2,
contextMenuBuilder: (context, editableTextState) {
return _MyContextMenu(
anchor: editableTextState.contextMenuAnchors.primaryAnchor,
children: AdaptiveTextSelectionToolbar.getAdaptiveButtons(
context,
editableTextState.contextMenuButtonItems,
).toList(),
);
},
);
}
}A full example of building a custom context menu can be found in the samples
repository in
custom_menu_page.dart
on GitHub.
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.Listed in the official breaking-changes index (docs.flutter.dev/release/breaking-changes).
An official announcement or migration guide documents an intended behaviour or API change. A verified source change (git diff) shows code changed but does not by itself prove a public API is breaking. AI interpretations are labelled and never used to classify a change.
- Official migration guide
- Simple custom text selection toolbars
- Right click menu outside of text fields
- Text editing for desktop - stable
- Ability to disable context menu on TextFields
- Missing APIs for text selection toolbar styling
- Enable copy toolbar in all widgets
- Disable context menu from browser
- Custom context menus don't show up for Flutter web
- ContextMenus
- Ability to disable the browser's context menu on web
- Ability to disable the browser's context menu on web (engine)
- Custom context menus in SelectableRegion on web
- Source commit
- b3045439b7c0
- Source last updated
- Oct 21, 2026
- Last synchronized
- Oct 21, 2026
- Publication status
- published
Publication history
- Oct 21, 2026, 8:31 PM: new → published (ingestion) — Backed by an official breaking-change/migration guide.
AI_PROVIDER (for example to openai-compatible with a local Ollama or LM Studio endpoint) to enable grounded explanations. Everything else on this page works without an AI provider.