Skip to content

Add menus

fakeivchenko edited this page Oct 9, 2026 · 1 revision

An application can have a menu bar, a context menu for a right click on the page, and menus that the page opens itself, all drawn by the platform. In this guide, a notes window gets a menu bar with File, Edit, and View, a context menu for its notes, and a Sort by button that opens a menu from the page.

A menu bar with the File menu open, on Linux and on Windows 11

Give the application a menu bar

application.menu sets the menu bar of every window. Each entry at the top level is a submenu:

public class Menus {
  public static void main(String[] args) {
    Application.launch(
        WindowParameters.of("Menus", 620, 440),
        window -> {
          window
              .application()
              .menu(
                  MenuItem.submenu(
                      "File",
                      MenuItem.of("New note", "CmdOrCtrl+N", () -> window.emit("command", "new")),
                      MenuItem.of("Export", "CmdOrCtrl+E", () -> window.emit("command", "export")),
                      MenuItem.separator(),
                      MenuItem.role(MenuRole.QUIT)),
                  MenuItem.editMenu(),
                  MenuItem.submenu(
                      "View",
                      MenuItem.checkbox(
                          "Show done", true, shown -> window.emit("showDone", shown))));
          window.contextMenu(
              MenuItem.of("Mark as done", () -> window.emit("command", "done")),
              MenuItem.of("Delete", () -> window.emit("command", "delete")),
              MenuItem.separator(),
              MenuItem.role(MenuRole.SELECT_ALL));
          window.loadResource("menus/index.html");
        });
  }
}
Entry What it does
MenuItem.of(label, action) Runs action when the user picks it.
MenuItem.of(label, accelerator, action) The same, and also on the keys, such as CmdOrCtrl+N, while a window of the application has the keyboard.
MenuItem.checkbox(label, checked, onToggle) A check mark that hears every change.
MenuItem.submenu(label, items...) A menu inside a menu.
MenuItem.separator() A line between groups of entries.
MenuItem.role(MenuRole.QUIT) An entry that the platform knows: UNDO, REDO, CUT, COPY, PASTE, SELECT_ALL, CLOSE_WINDOW, MINIMIZE, FULLSCREEN, or QUIT, with the label and the keys of the platform.
MenuItem.editMenu() An Edit menu with Undo, Redo, Cut, Copy, Paste, and Select All.
MenuItem.windowMenu() A Window menu with Minimize, Full Screen, and Close Window.

An action runs off the UI thread, so it can block or call back into a window. The keys of an entry come before the page: a page that listens to Ctrl+S itself doesn't hear the Ctrl+S of an entry. The keys of an editing role stay with the page, so text fields keep their copy and paste.

window.menu(...) gives one window a menu bar of its own, and an empty list takes the bar away from that window. window.useApplicationMenu() goes back to the menu bar of the application.

Add a context menu

window.contextMenu replaces the menu that the engine opens on a right click:

A context menu over the page, on Linux and on Windows 11

The page still decides first. A page that handles contextmenu and calls preventDefault() keeps the click to itself, so a text editor on the page can open its own menu. A check mark of a context menu keeps its state from one opening to the next. The accelerators of a context menu are only shown, because a context menu has no keys.

To open a menu from Java at the pointer, such as for a button of a custom title bar, call window.showContextMenu(items...). Its future completes when the menu closes.

Open a menu from the page

lwjwae.menu.popup opens a menu that the page describes, and resolves to the id of the entry that the user picked, or to null:

document.querySelector("#sort").addEventListener("click", async (event) => {
  const picked = await lwjwae.menu.popup([
    { id: "title", label: "Title" },
    { id: "date", label: "Date", checked: true },
    { separator: true },
    { id: "reverse", label: "Reverse order", checked: false }
  ], event);
  log(picked === null ? "Sort: nothing picked" : `Sort: ${picked}`);
});

A menu that the page opens under its button, on Linux and on Windows 11

An entry is { id, label, enabled, accelerator }, with checked for a check mark, submenu for a list of entries, role for a role such as "copy", or separator: true. The second argument places the menu: a mouse event, { x, y } in the coordinates of the page, or nothing for the pointer. Pass { signal } with an AbortSignal to close the menu from the page.

The page hears what Java emits for the entries of the menu bar and the context menu:

lwjwae.listen("command", ({ payload }) => log(`Command: ${payload}`));
lwjwae.listen("showDone", ({ payload }) => log(`Show done: ${payload}`));

Platforms

Platform The menu bar
Windows Under the title bar of every window. A window without a title bar doesn't show it.
macOS At the top of the screen, for the window in front. The application menu, with About, Hide, and Quit, stays first. Without an Edit menu, a text field of a page takes no Command+C, V, X, A, or Z.
Linux, GTK 3 and GTK 4 Above the page of every window, also in a window without a title bar.

Good to know

  • A menu is a value. A change, such as a new label, sets the menu again.
  • On macOS, a role in a menu of the page goes along the responder chain, as every Mac menu sends it, and the page hears of it as of no pick.
  • On Wayland, the compositor opens a menu only near the last click or key press that it saw, which is where the pointer is for a menu opened from a click.

Clone this wiki locally