Skip to content

Menus & Popovers

Four widgets share one idea: content or actions that stay hidden until the user asks for them, rather than occupying permanent space. Two (MenuButton, SplitButton) reuse the same <menu>/ <menuitem> vocabulary as the menu bar; the other two (Popover, Expander) host an arbitrary child tree instead of a menu.

Section titled “MenuButton (<menubutton>) and SplitButton (<splitbutton>)”

<menubutton> is a single button that opens a dropdown menu. <splitbutton> fuses two actions into one control: a primary click action plus a chevron that opens a dropdown for secondary actions. It’s AdwSplitButton on GTK and an NSButton + attached NSMenu on macOS.

Both take <menuitem> children, plus <menu> for a nested submenu, the same elements the <menubar> page documents, built into an NSMenu/GMenuModel instead of the app’s main menu:

<menubutton label="Actions" iconName="open-menu">
<menuitem label="Duplicate" onSelect={duplicate} />
<menuitem label="Rename" onSelect={rename} />
<menuitem role="separator" />
<menuitem label="Delete" iconName="edit-delete" onSelect={remove} />
</menubutton>
<splitbutton label="Save" iconName="document-save" onClick={save}>
<menuitem label="Save As…" onSelect={saveAs} />
<menuitem label="Save a Copy" onSelect={saveCopy} />
</splitbutton>
Widget Props Events
<menubutton> label, iconName (both createAndUpdate) none; each <menuitem> fires its own onSelect
<splitbutton> label, iconName (both createAndUpdate) clickedonClick (the primary action; the dropdown’s items fire their own onSelect)

An anchored transient surface (GtkPopover / NSPopover) for a small piece of arbitrary content attached to a trigger. Unlike MenuButton, its child is a full widget tree rather than <menuitem>s, so it can hold anything a <box> could.

const [open, setOpen] = useState(false);
<box orientation="horizontal" spacing={8}>
<button label="Open Popover" onClick={() => setOpen(true)} />
<popover open={open} position="bottom" onClosed={() => setOpen(false)}>
<box orientation="vertical" spacing={8} style={{ padding: 12 }}>
<label text="Popover content" />
<button label="Close" onClick={() => setOpen(false)} />
</box>
</popover>
</box>;
Prop Type Applied Notes
open bool createAndUpdate Controlled. Set it from onClosed when the user dismisses the popover by clicking outside or pressing Escape.
position top | bottom | left | right createAndUpdate Default top.

closedonClosed fires with no payload. A <popover> attaches to whatever widget is its own tree parent on GTK (gtk_widget_set_parent), so put it in a <box> alongside the button that opens it, as above, rather than off on its own.

An inline disclosure widget (AdwExpanderRow-style on GTK, an NSButton disclosure triangle + container on macOS) for content that should stay in the layout flow, unlike Popover, which floats above it.

const [open, setOpen] = useState(false);
<expander label="More options" expanded={open} onToggled={(e) => setOpen(e.checked)}>
<box orientation="vertical" spacing={6} style={{ padding: 8 }}>
<checkbox label="An option inside the expander" checked={/* ... */} onToggled={/* ... */} />
</box>
</expander>;
Prop Type Applied Notes
label string createAndUpdate
expanded bool createAndUpdate Controlled. Set it from onToggled.

toggledonToggled fires { checked }.

See examples/gallery/main.tsx’s “Popovers & Menus” tab for all four wired together, and the Widget Reference for the generated prop tables.