How to Debug Layout Issues and Resolve Widget ID Conflicts in Dear ImGui

Dear ImGui uses a stack-based ID system where widget identifiers are hashed from the current ID stack plus the widget label, and layout issues can be diagnosed using built-in debug overlays like ShowMetricsWindow and ShowIDStackToolWindow while ID conflicts are resolved using PushID/PopID or explicit ID suffixes.

When building sophisticated user interfaces with the ocornut/imgui repository, layout misalignments and state-sharing bugs between unrelated widgets are common pitfalls. These issues typically stem from Dear ImGui's internal ID generation mechanism and the implicit stack that tracks hierarchical context. Understanding how to leverage the library's built-in debugging utilities allows you to quickly identify clipping problems, positioning errors, and duplicate ID collisions.

Understanding the ID Stack Architecture in Dear ImGui

Every widget in Dear ImGui receives a unique ImGuiID generated by hashing the current ID stack state combined with the widget's label or pointer identifier. In imgui.h (line 610), the GetID overloads—ImGui::GetID(const char* str_id), GetID(void* ptr_id), and GetID(int int_id)—compute this hash by mixing the supplied value with the active stack context stored in ImGuiContext.

The core implementation resides in imgui_internal.h (line 2829), where internal functions GetID, GetIDFromPos, and GetIDFromRectangle serve as the workhorses feeding into all widget APIs. When you call a widget function like Button("OK"), Dear ImGui internally executes:

ImGuiID id = GetID("##OK");          // hash of current stack + "##OK"
ItemAdd(rect, id);

If the same stack state and label appear elsewhere in your code, the resulting hash becomes identical, triggering an ID conflict where Dear ImGui shares state (active, hovered, focused) between unrelated widgets.

Detecting and Debugging Layout Issues in Dear ImGui

Layout problems manifest as misplaced, clipped, or overlapping widgets, often caused by incorrect cursor positioning or parent window constraints. Dear ImGui provides several diagnostic tools to visualize these issues in real-time.

Visual Debug Overlays

The following helper functions, declared in imgui.h and implemented in imgui.cpp, draw immediate visual feedback over your interface:

  • DebugDrawItemRect - Outlines the last item's bounding rectangle in a specified color (defaults to red)
  • DebugDrawCursorPos - Displays the current cursor (pen) position used for layout calculations
  • ShowDebugLogWindow - Prints diagnostic messages including ID-generation logs
  • ShowMetricsWindow - Exposes full internal state including windows, draw lists, and active IDs

To use these utilities, ensure IMGUI_DISABLE_DEBUG_TOOLS is not defined in imconfig.h (line 38). A typical debugging workflow looks like:

if (ImGui::BeginMenuBar())
{
    if (ImGui::MenuItem("Debug##Layout"))
        ImGui::ShowMetricsWindow();   // toggles the debug window
    ImGui::EndMenuBar();
}

// Inside a problematic window:
ImGui::DebugDrawItemRect();           // draws a red outline around the last item
ImGui::DebugDrawCursorPos();          // shows where the next widget will be placed

These overlays let you compare the expected rectangle (GetItemRectMin/GetItemRectMax) with the actual rendered area, revealing misaligned calls to SetNextWindowPos, SetNextItemWidth, or unexpected BeginGroup/EndGroup nesting.

Programmatic Layout Queries

For precise debugging, query the layout state directly:

ImVec2 min = ImGui::GetItemRectMin();   // top-left of the last item
ImVec2 max = ImGui::GetItemRectMax();   // bottom-right of the last item
ImGuiID id  = ImGui::GetItemID();        // ID of the last item

If a widget appears collapsed or invisible, the rectangle may be zero-sized, indicating a missing SetNextItemWidth or a parent window that clipped it.

Resolving Widget ID Conflicts in Dear ImGui

When Dear ImGui detects two active items with the same ImGuiID, it increments DebugDrawIdConflictsCount (defined in imgui_internal.h line 2611) and highlights the overlapping IDs in the ID Stack Tool. Conflicts typically occur when labels are reused without unique prefixes in loops, when separate windows share identical implicit ID stacks, or when PushID/PopID misuse leaves the stack in an unexpected state.

Using the ID Stack Tool

The ID Stack Tool is your primary diagnostic for ID collisions. You can open it by pressing Ctrl+Alt+I (default) or calling ImGui::ShowIDStackToolWindow(). When active, hover over any widget to see the full stack trace of each PushID and Begin entry that contributed to that widget's ID.

The underlying data lives in ImGuiContext::DebugIDStackTool (imgui_internal.h line 2628). When a conflict is detected, DebugDrawIdConflictsId is set, causing the overlay to flash conflicting IDs in the Metrics window. The internal function DebugHookIdInfo (line 3846 in imgui_internal.h) is invoked from every GetID call when debug tools are active, allowing the stack overlay to show the complete generation path.

Best Practices for Unique IDs

Implement these patterns to prevent collisions:

Explicit ID suffixes - Append ## followed by a unique token to the label:

ImGui::Button("Save##File1");
ImGui::Button("Save##File2");  // Different ID despite same display text

PushID/PopID - Surround repeated sections with stack pushes:

for (int i = 0; i < num_entries; ++i)
{
    ImGui::PushID(i);                 // guarantees a unique stack element
    if (ImGui::Button("Delete"))
        DeleteEntry(i);
    ImGui::PopID();
}

GetIDWithSeed - Generate deterministic IDs with custom seeds when you need specific hash control:

ImGuiID id = ImGui::GetIDWithSeed("Item", mySeed);

If you cannot modify labels (e.g., third-party code), wrap regions with a unique PushID using a pointer or hash of the enclosing context.

Practical Debugging Examples

Debugging a Misaligned Window

// Toggle the metrics/debug windows
if (ImGui::Button("Show Debug")) ImGui::ShowMetricsWindow();

// Inside the window you suspect
ImGui::Text("Current cursor: (%.1f, %.1f)", 
            ImGui::GetCursorScreenPos().x,
            ImGui::GetCursorScreenPos().y);
ImGui::DebugDrawCursorPos();      // red dot at the cursor
ImGui::DebugDrawItemRect();       // red outline around the last widget

Running the above reveals if the cursor moved unexpectedly—such as after an extra SameLine() or missing SetNextWindowPos—by showing exactly where Dear ImGui thinks the next item should render.

Fixing Duplicate IDs in a List

for (int row = 0; row < rows; ++row)
{
    ImGui::PushID(row);                 // unique stack per row
    ImGui::Text("Row %d", row);
    ImGui::SameLine();
    if (ImGui::Button("Edit")) { /* … */ }
    ImGui::SameLine();
    if (ImGui::Button("Delete")) { /* … */ }
    ImGui::PopID();
}

Without PushID/PopID, the two Button("Edit") calls across rows share the same ID, causing the Edit button of the previously drawn row to activate when you click the current one.

Logging ID Conflicts Manually

void MyWidget()
{
    ImGuiID id = ImGui::GetID("MyWidget");
    if (ImGui::DebugDrawIdConflictsCount > 0)
        ImGui::LogWarning("ID conflict detected for %s!", "MyWidget");
    // Normal widget code…
}

Warnings appear in the Debug Log window opened via ImGui::ShowDebugLogWindow().

Summary

  • Dear ImGui generates widget IDs by hashing the current ID stack with the widget label or pointer, making stack management critical for avoiding conflicts.
  • Layout issues can be diagnosed using DebugDrawItemRect and DebugDrawCursorPos to visualize bounding boxes and cursor positions, or by opening ShowMetricsWindow for comprehensive state inspection.
  • ID conflicts are resolved by using PushID/**PopID** to create unique stack contexts, appending ##unique_suffix to labels, or utilizing GetIDWithSeed for deterministic hashing.
  • The ID Stack Tool (activated with Ctrl+Alt+I or ShowIDStackToolWindow) displays the complete ID generation path for hovered widgets, making collision sources immediately visible.
  • Ensure IMGUI_DISABLE_DEBUG_TOOLS is not defined in imconfig.h (line 38) to access the full debugging toolkit available in imgui.h and imgui_internal.h.

Frequently Asked Questions

What causes widget ID conflicts in Dear ImGui?

Widget ID conflicts occur when two widgets generate the same ImGuiID hash value, typically because they share identical labels within the same ID stack scope, such as buttons created inside a loop without unique identifiers. According to the ocornut/imgui source code in imgui_internal.h (line 2611), when Dear ImGui detects these collisions, it increments DebugDrawIdConflictsCount and can highlight the conflicting items in the ID Stack Tool.

How do I visualize widget bounding boxes for layout debugging?

Use ImGui::DebugDrawItemRect() immediately after the widget you want to inspect to draw a colored outline around its calculated rectangle, or call ImGui::DebugDrawCursorPos() to see the exact screen coordinates where Dear ImGui will place the next item. For comprehensive diagnostics, open ImGui::ShowMetricsWindow() to inspect window boundaries, draw lists, and item rectangles simultaneously.

What is the purpose of PushID and PopID in Dear ImGui?

PushID and PopID manipulate the internal ID stack to create unique namespaces for widgets, ensuring that identical labels in different contexts (such as buttons inside a loop or across multiple child windows) generate distinct ImGuiID hashes. As implemented in imgui.h (line 610), these functions prepend a unique value to the stack before GetID calculates the final hash, preventing state collisions between separate UI elements.

How do I enable debug tools if they are missing from my build?

Verify that IMGUI_DISABLE_DEBUG_TOOLS is not defined in your imconfig.h file (line 38), as this preprocessor directive strips out all debugging utilities including ShowMetricsWindow and the ID Stack Tool. Once enabled, the debug functions declared in imgui.h and implemented in imgui.cpp become available for diagnosing both layout issues and ID conflicts at runtime.

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 →