How to Create and Manage Popup Menus, Context Menus, and Modal Dialogs in Dear ImGui

Dear ImGui implements popups as specialized windows managed through BeginPopup(), OpenPopup(), and BeginPopupModal(), using an internal stack (ImGuiContext::BeginPopupStack) to handle hierarchical levels and automatic closing behavior.

Creating overlays that capture or pass through user input is a core UI pattern in the ocornut/imgui immediate-mode library. Unlike traditional retained-mode UI frameworks, Dear ImGui treats popup menus, context menus, and modal dialogs as transient window states that you declare every frame. Understanding the relationship between the open-request queue (g.OpenPopupStack) and the active popup stack is essential for building robust interfaces.

All popup variants in Dear ImGui share a common internal infrastructure defined in imgui.cpp. When you call OpenPopup(), the system pushes an identifier onto ImGuiContext::OpenPopupStack. During the next frame, BeginPopup() checks this stack, and if the ID matches, it pushes a new level onto ImGuiContext::BeginPopupStack via BeginPopupEx() (located around lines 12681–12697 in imgui.cpp).

Key architectural constraints from the source code:

  • Identifiers are relative to the current ID stack. As documented in imgui.h (lines 846–850), OpenPopup() and BeginPopup() must be called at the same ID level; otherwise, the popup will never match its open request.
  • Window flags are forwarded. BeginPopupEx() forwards your ImGuiWindowFlags to the underlying window creation, setting ImGuiWindowFlags_Popup or ImGuiWindowFlags_Modal internally.
  • Automatic lifecycle management. Popups close automatically when users click outside the bounds, press Esc, or when you explicitly call CloseCurrentPopup(). The closing logic resides in ClosePopupToLevel() (lines 12726–12736 in imgui.cpp).

Creating Standard Popup Menus

Standard popup menus are transient overlays that appear near a triggering widget and disappear when focus is lost. The API follows a request-then-build pattern:

  1. Request the popup with OpenPopup(const char* str_id) to mark it as open for the next frame.
  2. Begin the contents with BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0), which returns true only while the popup is active.
  3. End the block with EndPopup().
if (ImGui::Button("Options"))
    ImGui::OpenPopup("options_popup");

if (ImGui::BeginPopup("options_popup"))
{
    if (ImGui::MenuItem("Refresh"))  { /* handle refresh */ }
    if (ImGui::MenuItem("Settings")) { /* handle settings */ }
    ImGui::Separator();
    if (ImGui::MenuItem("Quit"))     { /* handle quit */ }
    ImGui::EndPopup();
}

Because popups are just windows, you can embed any ImGui widgets inside them, including tables, sliders, or child regions.

Implementing Context Menus

Context menus are specialized popups triggered by mouse input, typically right-clicks. Dear ImGui provides convenience helpers in imgui.h (lines 875–880) that combine OpenPopupOnItemClick() and BeginPopup() into a single call:

  • BeginPopupContextItem(): Attaches to the last drawn item's ID.
  • BeginPopupContextWindow(): Attaches to the current window background.
  • BeginPopupContextVoid(): Attaches to the full screen void (no window).

These wrappers default to the right mouse button but accept ImGuiPopupFlags to customize the trigger:

ImGui::Text("Right-click me");
if (ImGui::BeginPopupContextItem())  // Automatically uses last item ID
{
    if (ImGui::MenuItem("Delete"))    { /* ... */ }
    if (ImGui::MenuItem("Duplicate")) { /* ... */ }
    ImGui::EndPopup();
}

// Left-click version
if (ImGui::BeginPopupContextItem("left_context", ImGuiPopupFlags_MouseButtonLeft))
{
    // Left-click menu items
    ImGui::EndPopup();
}

Building Modal Dialogs

Modal dialogs block interaction with every other ImGui window until dismissed. They dim the background and optionally display a title bar. Use BeginPopupModal() instead of BeginPopup():

bool open = true;
if (ImGui::Button("Delete file"))
    ImGui::OpenPopup("confirm_delete");

if (ImGui::BeginPopupModal("confirm_delete", &open, ImGuiWindowFlags_AlwaysAutoResize))
{
    ImGui::Text("Are you sure you want to delete this file?");
    
    if (ImGui::Button("Yes"))
    {
        // Perform deletion logic
        ImGui::CloseCurrentPopup();  // Explicit close
    }
    ImGui::SameLine();
    if (ImGui::Button("Cancel"))
    {
        ImGui::CloseCurrentPopup();  // or set open = false
    }
    ImGui::EndPopup();
}

The bool* p_open parameter allows external control over the modal's lifetime. When passing nullptr, you must rely exclusively on CloseCurrentPopup() or the automatic close behaviors.

Managing Popup Lifetime and State

Understanding when popups close is critical for state management:

  • Implicit closing: Selecting a MenuItem or Selectable automatically closes the current popup level.
  • Explicit closing: Call CloseCurrentPopup() to dismiss the topmost popup immediately.
  • Focus loss: Clicking outside the popup rectangle or pressing Esc triggers ClosePopupToLevel(), unwinding the BeginPopupStack.

Because the popup system uses the ID stack, nested popups are fully supported. Each BeginPopup() call creates a new scope, and EndPopup() restores the previous state.

Summary

  • Dear ImGui popups are managed through OpenPopup(), BeginPopup(), and EndPopup(), with modal variants using BeginPopupModal().
  • Context menu helpers like BeginPopupContextItem() wrap input detection and popup creation for common right-click patterns.
  • Popup identifiers must match between OpenPopup() and BeginPopup() calls at the same ID stack depth, as enforced in imgui.h.
  • Automatic cleanup occurs via ClosePopupToLevel() when users click outside or press Esc, while CloseCurrentPopup() provides programmatic control.
  • Reference implementations for all patterns are available in imgui_demo.cpp, with core logic in imgui.cpp and API definitions in imgui.h.

Frequently Asked Questions

What is the difference between OpenPopup() and BeginPopup()?

OpenPopup() queues a request to open a popup on the next frame by pushing an ID onto ImGuiContext::OpenPopupStack. BeginPopup() checks that queue, and if the ID matches, it begins the actual window scope and returns true. You must call OpenPopup() before BeginPopup() will ever succeed.

Why is my popup not appearing when I call BeginPopup()?

The most common cause is an ID stack mismatch. According to the implementation in imgui.cpp, BeginPopup() compares the requested ID against g.OpenPopupStack using the current ID stack scope. If you called OpenPopup() inside a PushID() block but call BeginPopup() outside that block (or vice versa), the hashes will not match. Ensure both calls occur at the same stack level.

How do I programmatically close a modal dialog from within the popup?

Use CloseCurrentPopup(). This function (defined in imgui.h and implemented in imgui.cpp) marks the current popup level for closure, causing it to disappear at the end of the frame. Alternatively, if you passed a boolean pointer to BeginPopupModal() as the p_open argument, setting that boolean to false will also close the modal.

Can I nest popups inside other popups?

Yes. Dear ImGui supports nested popup hierarchies through ImGuiContext::BeginPopupStack. Each call to BeginPopup() pushes a new level onto this stack. Child popups must follow the same OpenPopup() then BeginPopup() pattern, and they will automatically close if the parent closes. The stack unwinds correctly when EndPopup() is called for each level.

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 →