Skip to content
heliguy4599 edited this page Jul 7, 2026 · 5 revisions

Gio.Menu is GTK's declarative model for menus, popovers, and menu bars. Menu items are usually tied to actions by name, so they can activate a Gio.SimpleAction or Gio.PropertyAction without needing a reference to the widget that owns it. GObjectify's Menu helper builds these menus directly from the actions of a class extending from, without needing an instance.

Background - Menus in Plain GJS

In plain GJS, building a menu usually means manually constructing Gio.MenuItems and setting their detailed action names and targets by hand, all without any type safety over the backing action's param or state types.

const menu = new Gio.Menu()

const save_item = new Gio.MenuItem()
save_item.set_label("Save")
save_item.set_detailed_action("win.save")
menu.append_item(save_item)

const sort_section = new Gio.Menu()

const name_item = new Gio.MenuItem()
name_item.set_label("Name")
name_item.set_detailed_action("win.sort_order")
name_item.set_attribute_value("target", new GLib.Variant("s", "name"))
sort_section.append_item(name_item)

const date_item = new Gio.MenuItem()
date_item.set_label("Date")
date_item.set_detailed_action("win.sort_order")
date_item.set_attribute_value("target", new GLib.Variant("s", "date-created"))
sort_section.append_item(date_item)

menu.append_item(Gio.MenuItem.new_section(null, sort_section))

Every item requires repeated, un-typed calls, and there's nothing stopping a typo in an action, or a target of the wrong GVariant type, from silently doing nothing, or causing a runtime crash.

The GObjectify Way

// With this class and its SimplerActions...
@GClass()
class MainWindow extends from(Gtk.ApplicationWindow, {
	save: SimplerAction.void(),
	sort_order: SimplerAction.state.string("name").as<"name" | "date-created">(),
})

// We build this menu
const menu = Menu.build(
	Menu.item(MainWindow, "save", "Save"),
	Menu.section(null,
		Menu.item_group(MainWindow, "sort_order",
			{ label: "Name", target: "name" }, // type-checked
			{ label: "Date", target: "date-created" }, // type-checked
		),
	),
)

Menu reads a class's static $actions (aded automatically by GClass and from) to resolve detailed action names and target types. This means there's no instance to create or reference, and no action or target value to get wrong. The action and target are both checked by TypeScript agains the action's declared kind and type.

Menu.item - Single Items

Creates one Gio.MenuItem targeting a single action on a class.

Menu.item(MainWindow, "undo", "Undo")
Menu.item(MainWindow, "save", { label: "Save", icon: "document-save-symbolic" })
Menu.item(MainWindow, "set_theme", { label: "Dark", target: "dark" })

For void actions, config may be a plain string, used directly as the label. For param, state, and prop actions, config must be an object that includes a target, typed to match that action's parameter type.

Menu.item_group - Radio Button Group

Creates several Gio.MenuItems that all target the same action with different target values, the standard pattern for radio selections in a menu.

Menu.item_group(MainWindow, "sort_order",
	{ label: "Name", target: "name" },
	{ label: "Date", target: "date-created" },
	{ label: "Size", target: "size" },
)

Only state and prop kind actions are accepted, since void and param actions have no persisten state to represent which item is currently selected.

Menu.items_for - Bulk Creation

Builds several items across several actions on the same class in one call, useful when a menu mostly mirrors a class's actions one-to-one.

Menu.items_for(MainWindow, {
	undo: "Undo",
	save: { label: "Save", icon: "document-save-symbolic" },
	sort_order: [
		{ label: "Name", target: "name" },
		{ label: "Date", target: "date-created" },
		{ label: "Size", target: "size" },
	],
})

Each key is an action name on the class, and each value is whatever Menu.item (or, for state/prop actions, Menu.item_group) would accept for that action. Passing an array always builds a group, passing a single config or string always builds one item.

Item Options

Menu items have the following options, which allow for further customization.

  • label: the item's display text, or null for no label
  • icon: either an icon name string, or a Gio.Icon
  • hidden_when: hide the item from the menu under certain conditions ("action-disabled", "action-missing", "macos-menubar")
  • target: type-matched value to provide when activating the action (only for param, state, and property actions)

Sections and Submenus

Menu.section and Menu.submenu group items together in a Gio.MenuItem wrapper, which will then become part of a section or submenu nested on another menu. The items may either be provided one-by-one, or an array of menu items, such as what item_group and items_for return.

Menu.section("Edit",
	Menu.item(MainWindow, "save", "Save"),
	Menu.item(MainWindow, "discard_changes", "Discard Changes"),
)

Menu.submenu("Theme",
	Menu.item_group(MainWindow, "set_theme",
		{ label: "Light", target: "light" },
		{ label: "Dark", target: "dark" },
	),
)

Both accept any mix of individual Gio.MenuItems and arrays of them, so the output of item_group and items_for can be passed straight through.

Menu.build - Assembling a Menu

The build function assembles a top-level Gio.Menu out of items, sections, and subments. It accepts a mix of individual items, and a arrays of items.

const menu = Menu.build(
	Menu.item(MainWindow, "quit", "Quit"),
	Menu.submenu("Edit",
		Menu.item(MainWindow, "save", "Save"),
	),
	Menu.section("Theme",
		Menu.item_group(MainWindow, "set_theme",
			{ label: "Light", target: "light" },
			{ label: "Dark", target: "dark" },
		),
	),
)

The result is a plain Gio.Menu, usable anywhere a Gio.MenuModel is expected, such as a GtkPopover's menu model, a GtkApplications's menubar, or a GtkMenuButton's menu.

Clone this wiki locally