Creating Popup Modals and Context Menus Correctly in Dear ImGui

Dear ImGui implements popups as a self-contained stack inside the global ImGuiContext, requiring matched OpenPopup and BeginPopup calls at the same ID stack depth to display modal windows and context menus correctly.

Dear ImGui (ocornut/imgui) provides a flexible popup system for creating modal dialogs and right-click context menus, but correct usage depends on understanding the internal ID stack and the two-phase open-begin lifecycle. This guide explains how to create popup modals and context menus correctly in Dear ImGui by referencing the actual source implementation in imgui.cpp and imgui_internal.h and avoiding common pitfalls that break the popup state machine.

How Dear ImGui Manages Popup State

Popups in Dear ImGui are not standalone windows but entries in a per-frame stack stored in g.BeginPopupStack (declared at imgui_internal.h#L2373). When you open a popup, the library creates an ImGuiPopupData entry that persists only while the popup remains active. The identifier for each popup is resolved against the current ID stack, which is why OpenPopup and BeginPopup must be called at the exact same nesting level—mismatched depth causes the lookup to fail silently.

The Four-Step Popup Lifecycle

All popups follow a strict lifecycle defined by four core functions from imgui.h:

  1. OpenPopup: Marks the popup as open by pushing its ID onto the internal stack. This must be called once per triggering event, not every frame. Available overloads include OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags = 0) at imgui.h#L59 and an ImGuiID variant for manual ID handling.

  2. BeginPopup or BeginPopupModal: Queries the stored state. If the popup is open, it creates an ImGuiWindow and returns true so you can populate contents. BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0) is at imgui.h#L848, while BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0) is at imgui.h#L849. The modal variant automatically sets ImGuiWindowFlags_Modal (defined at imgui.h#L1198-L1199), blocking interaction with background windows and adding a dimmed overlay.

  3. EndPopup: Closes the window created by BeginPopup*. Must be called only if the corresponding begin function returned true, located at imgui.h#L850.

  4. CloseCurrentPopup (optional): Forces the current popup to close from within its own code block, defined at imgui.h#L62.

Context Menu Helpers

For right-click context menus, Dear ImGui provides combined helpers that handle both the open trigger and the begin call automatically:

  • BeginPopupContextItem: Opens when right-clicking the last item. Signature at imgui.h#L873.
  • BeginPopupContextWindow: Opens when right-clicking anywhere in the current window. Signature at imgui.h#L874.
  • BeginPopupContextVoid: Opens when right-clicking the background void (no window). Signature at imgui.h#L875.

These functions internally call OpenPopupOnItemClick using the right mouse button by default, then immediately call BeginPopup. Pass ImGuiPopupFlags_NoOpenOverExistingPopup to popup_flags to prevent overlapping menus.

The ID Stack Discipline

The most common source of popup failures is violating the ID stack rule. Because popup IDs are hashed relative to the current ID stack, calling OpenPopup inside a tree node (TreeNode) or group (BeginGroup) means you must call the matching BeginPopup inside that same node or group. If the nesting levels differ, the hashed IDs will not match and the popup will never appear. Always ensure OpenPopup and BeginPopup occur at the same scope level.

Common Pitfalls to Avoid

  • Calling OpenPopup every frame: This keeps the popup perpetually open because the library interprets it as a continuous open request. Call it only on the specific event (button press, right-click) that triggers the popup.
  • Mismatched ID depth: Opening in one container and beginning in another breaks the ID lookup.
  • Forgetting EndPopup: Skipping this after a successful BeginPopup corrupts the internal BeginPopupStack and causes subsequent popups to behave erratically.
  • Wrong mouse button defaults: Prior to v1.92.6, context menus defaulted to the left button; current versions use the right button. Explicitly pass ImGuiPopupFlags_MouseButtonLeft or ImGuiPopupFlags_MouseButtonRight if you need specific behavior.

Practical Code Examples

Simple Modal Dialog

if (ImGui::Button("Show modal"))
    ImGui::OpenPopup("my_modal");

if (ImGui::BeginPopupModal("my_modal", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
    ImGui::Text("This is a modal dialog.");
    if (ImGui::Button("Close"))
        ImGui::CloseCurrentPopup();
    ImGui::EndPopup();
}

The modal blocks all interaction behind it and automatically dims the background.

Context Menu on an Item

ImGui::Text("Right-click me");
if (ImGui::BeginPopupContextItem())
{
    if (ImGui::MenuItem("Delete")) { /* handle delete */ }
    if (ImGui::MenuItem("Rename")) { /* handle rename */ }
    ImGui::EndPopup();
}

No explicit OpenPopup call is required—the helper handles the open and begin logic.

Custom Popup from a Button

if (ImGui::Button("Open custom popup"))
    ImGui::OpenPopup("custom_popup");

if (ImGui::BeginPopup("custom_popup"))
{
    ImGui::Text("Custom content");
    if (ImGui::Button("Close"))
        ImGui::CloseCurrentPopup();
    ImGui::EndPopup();
}

Call OpenPopup only on the button press to create dropdown-like behavior.

Context Menu on Void (Background)

if (ImGui::BeginPopupContextVoid())
{
    if (ImGui::MenuItem("Add item")) { /* handle add */ }
    ImGui::EndPopup();
}

Triggers when the user right-clicks the background where no widget is present.

Key Implementation Files

Understanding the popup system requires familiarity with three core files:

  • imgui.h: Public API declarations for BeginPopup*, OpenPopup*, and context-menu helpers.
  • imgui_internal.h: Internal structures including ImGuiPopupData and BeginPopupStack.
  • imgui.cpp: Implementation of BeginPopupEx, modal handling, and stack logic.

Real-world usage examples are available in examples/example_sdl2_opengl3/main.cpp and examples/example_win32_directx11/main.cpp.

Summary

  • Popups are stack-based: State lives in g.BeginPopupStack and is evaluated per-frame.
  • Match your IDs: OpenPopup and BeginPopup must share the same ID stack depth.
  • Trigger once: Call OpenPopup only on the event that opens the popup, not every frame.
  • Use helpers: BeginPopupContextItem and BeginPopupContextVoid simplify context menu creation.
  • Always pair: Every successful BeginPopup or BeginPopupModal must be followed by EndPopup.

Frequently Asked Questions

Why does my popup fail to appear even though I called OpenPopup?

This typically occurs due to an ID stack mismatch. If you call OpenPopup inside a TreeNode, PushID, or BeginGroup block, you must call the corresponding BeginPopup inside that same block before the ID is popped. The hashed identifier includes the current stack scope, so differing depths produce different IDs.

What is the difference between BeginPopup and BeginPopupModal?

BeginPopup creates a standard popup window that closes when the user clicks outside of it. BeginPopupModal (at imgui.h#L849) creates a modal dialog that blocks interaction with all other windows, automatically dims the background, and typically requires an explicit user action (button press or CloseCurrentPopup) to dismiss.

How do I open a context menu with the left mouse button instead of right?

Pass ImGuiPopupFlags_MouseButtonLeft to the popup_flags parameter of BeginPopupContextItem, BeginPopupContextWindow, or BeginPopupContextVoid. For example: BeginPopupContextItem(NULL, ImGuiPopupFlags_MouseButtonLeft). If using the manual OpenPopup approach, call OpenPopupOnItemClick with the desired button flag.

When should I call CloseCurrentPopup versus letting the popup close automatically?

Call CloseCurrentPopup (defined at imgui.h#L62) when you need to dismiss the popup immediately upon a specific user action, such as clicking a "Confirm" or "Cancel" button inside a modal dialog. For non-modal popups, you can simply let the user click outside the window to close it naturally, which terminates the popup when BeginPopup returns false on the next frame.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →