skip to content

What does Flutter's PlatformMenuBar do on macOS, and why does a Flutter app on Windows or Linux need a different menu?

level: middleimportance: nice to knowfreq 18%

answer

  1. the system menu bar at the top
  2. native rendering through a delegate
  3. macOS support out of the box only
  4. PlatformProvidedMenuItem for About and Quit
  5. MenuBar widget in the window

basics

~20 s

PlatformMenuBar describes PlatformMenu and PlatformMenuItem entries that macOS renders in its native menu bar; Flutter only supports that out of the box on macOS. Windows and Linux need an in-window menu such as Material's MenuBar.

solid answer

~40 s

`PlatformMenuBar` takes `menus`, a list of `PlatformMenu` and `PlatformMenuItem` objects, and hands them to the platform through `WidgetsBinding.platformMenuDelegate`. It draws nothing itself and just returns its `child`; the host renders the menu. Flutter only includes support for **macOS** out of the box, where every app is expected to have the system menu bar with items like About, Hide and Quit, provided through `PlatformProvidedMenuItem` types. On Windows and Linux the widget has no menu to show unless a plugin supplies a delegate, and `PlatformProvidedMenuItem` asserts in debug for types the platform lacks; check `PlatformProvidedMenuItem.hasMenu`. So cross-platform apps use `PlatformMenuBar` on macOS and a Material `MenuBar` with `SubmenuButton` and `MenuItemButton` inside the window elsewhere, sharing the same actions. Only one `PlatformMenuBar` may be active per delegate.

code

dart · 35 lines
dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class InvoiceMenus extends StatelessWidget {
  const InvoiceMenus({super.key, required this.onNewInvoice, required this.child});

  final VoidCallback onNewInvoice;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return PlatformMenuBar(
      menus: <PlatformMenuItem>[
        const PlatformMenu(
          label: 'Shop Invoices',
          menus: <PlatformMenuItem>[
            PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.about),
            PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.quit),
          ],
        ),
        PlatformMenu(
          label: 'File',
          menus: <PlatformMenuItem>[
            PlatformMenuItem(
              label: 'New Invoice',
              shortcut: const SingleActivator(LogicalKeyboardKey.keyN, meta: true),
              onSelected: onNewInvoice,
            ),
          ],
        ),
      ],
      child: child,
    );
  }
}

go deeper

for a junior

Recall that PlatformMenuBar creates the macOS system menu and that Windows and Linux use an in-window MenuBar.

for a middle

Explain that PlatformMenuBar only forwards data through the platform menu delegate, which macOS implements, and what PlatformProvidedMenuItem.hasMenu guards.

for a senior

Show how you share one set of actions between the macOS system menu and the in-window menus elsewhere, including shortcut behaviour.

for a principal

Decide how far each desktop build follows its platform's menu conventions and what that costs in duplicated menu definitions.

## Two kinds of menu bar Desktop platforms put the application menu in different places: | Platform | Where the menu lives | Flutter widget | |---|---|---| | macOS | the **system menu bar** at the top of the screen, shared by all apps | `PlatformMenuBar` | | Windows, Linux | usually **inside the window**, under the title bar | `MenuBar` from the Material library | A Mac app without a proper menu bar feels broken: users look for About, Preferences, Hide and Quit there. A Windows or Linux user expects File and Edit menus inside the window. ## How PlatformMenuBar works - It takes `menus`: `PlatformMenu(label:, menus:)` for top-level and nested menus, `PlatformMenuItem(label:, shortcut:, onSelected:)` for actions, `PlatformMenuItemGroup` to group items between separators, and `PlatformProvidedMenuItem(type:)` for system-supplied items. - It **draws nothing**. Its build returns its `child`; the menu data goes to the platform through `WidgetsBinding.platformMenuDelegate`, and the host platform renders the menu and handles its shortcuts. - The widget is in the tree mainly as a convenient way to **update** the menu: rebuild it with new items, such as a checked state, and the delegate sends the change. - There can be only **one** `PlatformMenuBar` using a given delegate at a time; a second one asserts. ## Platform-provided items `PlatformProvidedMenuItemType` covers items the OS implements, such as `about`, `quit`, `servicesSubmenu`, `hide`, `hideOtherApplications`, `toggleFullScreen` and `minimizeWindow`. They are only available on macOS: `PlatformProvidedMenuItem.hasMenu(type)` returns `false` on Windows, Linux and the mobile platforms, and sending an unsupported type asserts in debug mode. Call `hasMenu` before adding one in shared code. ## Why Windows and Linux need something else Flutter ships a platform menu delegate that macOS understands. On Windows and Linux there is no built-in native implementation, so a `PlatformMenuBar` there shows no menu unless a plugin installs its own delegate. The practical pattern: 1. Define the app's actions once, for example as callbacks or `Intent`s. 2. On macOS, build a `PlatformMenuBar` whose items call those actions, including `PlatformProvidedMenuItem`s for About and Quit. 3. On Windows and Linux, place a Material `MenuBar(children: [...])` at the top of the window, with `SubmenuButton(menuChildren: [...], child: ...)` for File and Edit and `MenuItemButton(onPressed:, shortcut:, child:)` for actions. 4. Choose between them with `defaultTargetPlatform`. ## Shortcuts in menus Both `PlatformMenuItem.shortcut` and `MenuItemButton.shortcut` accept a `SingleActivator`, for example Command-N on macOS or Control-N elsewhere. On macOS the system menu handles the key combination itself. With `MenuItemButton`, the shortcut is shown as a label; making the key combination work when the menu is closed is a separate job for shortcut and action widgets. ## Common mistakes - Expecting `PlatformMenuBar` to draw a menu strip in the window on Windows; it shows nothing there without a plugin delegate. - Placing a `PlatformMenuBar` on several screens at once; only one may use the delegate, and a second asserts. - Adding `PlatformProvidedMenuItem`s in shared code without `hasMenu`, which asserts in debug builds off macOS. - Defining menu actions twice with different behaviour on each platform instead of sharing one set of callbacks. ## Worked example A small-shop invoice tool on macOS shows "Shop Invoices" with About and Quit, a File menu with New Invoice and Export, and an Edit menu. The Windows build shows the same File and Edit menus as a `MenuBar` inside the window. Both call the same `newInvoice()` and `exportCsv()` functions.

  • Shared Flutter code adds PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.quit) and a debug build asserts on Windows. Why?
    Platform-provided items are only available on macOS. `PlatformProvidedMenuItem.hasMenu` returns `false` on Windows and Linux, and serialising an unsupported type throws an `ArgumentError` in debug mode. Guard it with `hasMenu`, or build the Windows menu with Material's `MenuBar` instead.

saying these in an interview costs you the question

  • PlatformMenuBar draws a menu bar inside the Flutter window on every platform.
  • PlatformMenuBar gives Windows a native menu with no plugin.
  • You can nest several PlatformMenuBars for different screens.
  • About and Quit must be hand-built on macOS.