How to Manage Widget IDs and Prevent Collisions in Dear ImGui Using the ID Stack

Dear ImGui generates distinct 32-bit ImGuiID values by hashing the current ID stack, which developers manipulate via PushID() and PopID() calls to scope widgets and eliminate collisions when multiple items share identical visible labels.

Every interactive element in Dear ImGui (the ocornut/imgui repository) requires a unique identifier to track focus, hover states, and persistent data. The library derives these IDs by hashing a runtime stack of identifiers that you control. When you need to manage widget IDs and prevent collisions in Dear ImGui using the ID stack, you are essentially manipulating this hierarchical hashing system to ensure every button, slider, or tree node receives a distinct signature even when their visual labels are identical.

Understanding the ID Stack Architecture

Dear ImGui uses a 32-bit ImGuiID to distinguish every interactable widget. This identifier is not assigned manually but computed by hashing the ID stack—a dynamic array of string pointers, memory addresses, and integers that you push and pop at runtime.

As implemented in imgui.cpp (lines 9357–9381), the core API provides three overloads of PushID() and a corresponding PopID():

  • ImGui::PushID(const char* str_id) – Hashes a null-terminated string onto the stack.
  • ImGui::PushID(const void* ptr_id) – Hashes a raw pointer (useful for object addresses).
  • ImGui::PushID(int int_id) – Hashes a 32-bit integer (ideal for loop indices).
  • ImGui::PopID() – Removes the top-most identifier, restoring the previous stack state.

The final ID for any widget is the cumulative hash of all items currently in this stack combined with the widget's visible label (or hash of "##" hidden labels). This design allows multiple widgets to share the same display text without conflict, provided they exist within different ID scopes.

Preventing Collisions: The PushID/PopID Workflow

Collisions occur when two active widgets produce identical ImGuiID values, causing ImGui to merge their states—resulting in flickering focus, shared hover detection, or lost interaction. This happens most frequently when generating widgets inside loops or recursive functions where visible labels repeat.

The Three-Step Safety Pattern

To guarantee uniqueness, wrap related widgets with a scoped identifier:

  1. Push a unique base before creating the group. Use the loop index, object pointer, or a descriptive string.
  2. Create widgets using short or repeating labels (often with ## hidden identifiers).
  3. Pop the identifier immediately after the group ends to prevent scope leakage.

According to the source code in imgui_tables.cpp (line 1593), the library validates stack integrity with IM_ASSERT_USER_ERROR, ensuring every PushID has a matching PopID when tables or scopes exit.

Practical Implementation Patterns

Loop Iteration with Integer IDs

When iterating containers, push the array index to distinguish otherwise identical buttons:

for (int i = 0; i < items.size(); i++) {
    ImGui::PushID(i);  // Unique per iteration
    if (ImGui::Button("Delete")) {
        items.erase(items.begin() + i);
    }
    ImGui::PopID();
}

This pattern appears throughout imgui_demo.cpp (e.g., lines 961, 1318, and 3463), demonstrating how integer IDs isolate dynamic list elements.

Object Pointers for Hierarchical Trees

For tree nodes or recursive structures, use the object's memory address as the identifier:

void DrawNode(Node* node) {
    ImGui::PushID(node);  // Unique per object instance
    bool open = ImGui::TreeNode("##Node", "%s", node->name.c_str());
    ImGui::PopID();
    
    if (open) {
        for (Node* child : node->children) {
            DrawNode(child);
        }
        ImGui::TreePop();
    }
}

As seen in imgui_widgets.cpp (lines 2910–2920), internal tree widget implementations rely on this pointer-based approach to handle duplicate display names across different hierarchy levels.

Reusable Components with String Scopes

When building composable widgets that might appear multiple times in the same window, push a descriptive string:

void DrawColorEditor(const char* context_id, ImVec4* color) {
    ImGui::PushID(context_id);  // e.g., "PlayerHUD" vs "EnemyHUD"
    ImGui::ColorEdit4("##Color", (float*)color);
    ImGui::PopID();
}

Internal Dear ImGui widgets such as ColorEdit and Slider use similar stack manipulation, visible in imgui_widgets.cpp at lines 844 and 1230, to ensure nested composite controls do not collide with sibling elements.

Debugging and Validation in the Source Code

When IDs collide, Dear ImGui provides debugging tools to inspect the active stack. The Metrics/Debug window (accessible via ImGui::ShowMetricsWindow()) displays the full ID stack for the hovered widget. This tooltip logic is implemented in imgui.cpp at line 18512 within the MetricsHelpMarker functionality.

If you encounter assertion failures, check imgui_tables.cpp line 1593 where IM_ASSERT_USER_ERROR validates that ID stack depth matches expected scope boundaries, particularly when closing tables or menus. Mismatched PushID/PopID pairs trigger these diagnostics immediately.

Summary

  • Dear ImGui assigns every widget a 32-bit ImGuiID computed by hashing the current ID stack combined with the label.
  • PushID accepts strings, pointers, or integers to inject uniqueness into the hash, while PopID restores the previous scope.
  • Always wrap loops, recursive trees, and reusable components with PushID/PopID pairs to prevent collisions between widgets sharing visual labels.
  • Validate your code using the Metrics window (line 18512 in imgui.cpp) and ensure matching push/pop calls to avoid assertions at imgui_tables.cpp:1593.

Frequently Asked Questions

What happens if two Dear ImGui widgets have the same ID?

When two active widgets share the same ImGuiID, Dear ImGui treats them as the same logical item. This causes focus, hover, and activation states to conflict, resulting in flickering inputs, stuck buttons, or lost interactions. The library requires every interactable element to possess a unique identifier derived from the ID stack and label combination.

Can I use the ID stack with Dear ImGui tables and columns?

Yes. Dear ImGui automatically manipulates the ID stack internally when creating tables to isolate cell contents. As shown in imgui_tables.cpp at line 1593, the table system validates stack depth with IM_ASSERT_USER_ERROR to ensure that row and cell identifiers do not leak outside table boundaries. You should still push your own IDs when placing interactive widgets inside table cells.

Is PushID(int) or PushID(void*) better for performance?

Both PushID(int) and PushID(const void*) generate equivalent hashing overhead because Dear ImGui hashes the binary representation of the pointer or integer value directly. According to the implementation in imgui.cpp (lines 9357–9381), choose pointers for stable object references (like tree nodes) and integers for transient loop indices. Avoid hashing large strings when possible, as pointer and integer hashes involve less computation.

How do I debug which ID stack entry is causing a collision?

Open the Metrics/Debugger window using ImGui::ShowMetricsWindow() and hover over the problematic widget. The tooltip displays the full ID stack hash and scope depth, implemented in imgui.cpp at line 18512. Compare the stack entries between colliding widgets to identify where you failed to push a unique identifier or missed a PopID call.

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 →