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.
Popup Architecture and the Internal Stack
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()andBeginPopup()must be called at the same ID level; otherwise, the popup will never match its open request. - Window flags are forwarded.
BeginPopupEx()forwards yourImGuiWindowFlagsto the underlying window creation, settingImGuiWindowFlags_PopuporImGuiWindowFlags_Modalinternally. - Automatic lifecycle management. Popups close automatically when users click outside the bounds, press Esc, or when you explicitly call
CloseCurrentPopup(). The closing logic resides inClosePopupToLevel()(lines 12726–12736 inimgui.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:
- Request the popup with
OpenPopup(const char* str_id)to mark it as open for the next frame. - Begin the contents with
BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0), which returnstrueonly while the popup is active. - 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
MenuItemorSelectableautomatically 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 theBeginPopupStack.
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(), andEndPopup(), with modal variants usingBeginPopupModal(). - Context menu helpers like
BeginPopupContextItem()wrap input detection and popup creation for common right-click patterns. - Popup identifiers must match between
OpenPopup()andBeginPopup()calls at the same ID stack depth, as enforced inimgui.h. - Automatic cleanup occurs via
ClosePopupToLevel()when users click outside or press Esc, whileCloseCurrentPopup()provides programmatic control. - Reference implementations for all patterns are available in
imgui_demo.cpp, with core logic inimgui.cppand API definitions inimgui.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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →