Avoiding ID Collisions in Dear ImGui: Complete Guide to Widget ID Management
Dear ImGui generates unique widget identifiers by hashing the visible label combined with the current ID stack; collisions occur when multiple widgets share identical labels within the same scope, and you resolve them by appending hidden ## suffixes or wrapping calls with ImGui::PushID() and ImGui::PopID().
When building complex user interfaces with the ocornut/imgui immediate-mode library, developers frequently encounter ID collisions that cause widgets to share state, focus, and interaction incorrectly. This happens because Dear ImGui derives every interactive element's identifier using the formula final_id = hash(all_id_stack_entries + visible_label), meaning identical labels in identical scopes produce identical hashes. Understanding how to manipulate the ID stack is essential for creating robust interfaces with duplicate labels or dynamically generated elements.
Why Collisions Happur in Dear ImGui
ID collisions manifest when two widgets receive the same hash value, causing the UI library to treat them as a single underlying element. This commonly occurs in two scenarios:
- Static duplicates: Multiple
ImGui::Button("Play")calls within the same window without additional scope differentiation - Dynamic generation: Loops that create identical widgets without pushing distinguishing identifiers onto the stack
The collision mechanism is evident in the core implementation within imgui.cpp (lines 9355-9380), where the PushID functions manipulate an internal stack that gets hashed alongside every widget label.
Three Reliable Strategies for Avoiding ID Collisions
Dear ImGui provides three complementary approaches to ensure unique identifiers, all documented in the official FAQ (lines 88-106 and 126-138).
Strategy 1: Append Hidden ## Suffixes to Labels
The ## syntax allows you to append invisible text to a label that contributes to the ID hash without affecting the visible interface. The text following ## is hashed into the ID but never rendered.
ImGui::Begin("Audio Controls");
ImGui::Button("Play"); // ID = hash("Audio Controls", "Play")
ImGui::Button("Play##Track1"); // ID includes "Play##Track1"
ImGui::Button("Play##Track2"); // ID includes "Play##Track2"
ImGui::End();
Use this approach when the number of widgets is known at compile-time or when you can manually assign unique names to each instance.
Strategy 2: Wrap Widgets with PushID/PopID
The most robust method for dynamic content involves explicitly pushing distinguishing values onto the ID stack before creating widgets. As implemented in imgui.cpp, PushID() accepts integers, pointers, or strings.
ImGui::Begin("Object List");
for (int i = 0; i < objects.size(); ++i) {
// Prefer stable identifiers over loop indices to survive reordering
ImGui::PushID(objects[i]->Name); // or PushID(objects[i])
ImGui::Button("Select"); // Visible label remains "Select"
ImGui::PopID();
}
ImGui::End();
According to the FAQ (lines 126-138), this is the "most convenient way of distinguishing ID when iterating and creating many UI elements programmatically." Tree nodes automatically perform this push operation, which is why nesting widgets under TreeNode() calls prevents collisions without manual intervention.
Strategy 3: Combine Both Approaches for Complex Hierarchies
For intricate interfaces with multiple nesting levels, combine PushID scopes with ## suffixes to create redundant disambiguation layers.
ImGui::Begin("Scene");
for (auto &node : scene.Nodes) {
ImGui::PushID(node); // Unique per node object
if (ImGui::TreeNode("##Node")) { // Hidden label keeps tree node unique
ImGui::Button("Delete##Btn"); // ID = hash(node, "Delete##Btn")
ImGui::TreePop();
}
ImGui::PopID();
}
ImGui::End();
This pattern appears in imgui_demo.cpp (lines 955-973), which demonstrates proper ID handling in the built-in demo window.
Implementation Details from Source Code
The architecture behind Dear ImGui's ID system resides in several key files:
imgui.cpp(lines 9355-9380): Contains the overloadedPushIDimplementations that hash integers, pointers, and strings into stack entriesimgui.h: Declares the public API forPushID/PopIDand documents label conventionsimgui_demo.cpp(lines 955-973): Provides working examples of ID stack manipulation in loops and tree structuresdocs/FAQ.md(lines 88-106, 145-158): Authoritative documentation stating that using##"solves simple collision cases" whilePushID/PopIDcreates scopes for programmatic UI generation
When using empty labels, always include a ## prefix with unique text (e.g., "##unique") to ensure the widget receives a valid hash rather than a collision-prone empty string.
Summary
-
ID collisions occur when identical labels share the same ID stack scope, causing widgets to share state and focus
-
The ## suffix adds hidden uniqueness to labels at compile-time without changing the visible text
-
PushID/PopID creates runtime scopes using integers, pointers, or strings, making it ideal for dynamic lists and loops
-
Stable identifiers (object pointers or names) survive reordering better than array indices
-
Tree nodes automatically manipulate the ID stack, providing implicit scope separation for nested widgets
Frequently Asked Questions
What causes ID collisions in Dear ImGui?
ID collisions occur when two interactive widgets generate identical hash values, which happens when they share the same visible label within the same ID stack scope. Because Dear ImGui computes final_id = hash(stack_entries + label), any combination of matching labels and matching stack contexts produces the same identifier, forcing the UI to treat distinct visual elements as a single logical entity.
How do I handle widget IDs inside loops?
Wrap your widget creation calls with ImGui::PushID() and ImGui::PopID(), passing a stable unique identifier such as an object pointer or name string rather than the loop index. According to the imgui_demo.cpp examples, using indices alone risks losing widget state if the collection gets reordered, whereas stable identifiers maintain consistent ID-to-object mapping across frames.
Can I use completely invisible labels in Dear ImGui?
Yes. Use the "##uniqueidentifier" syntax where the text following ## provides the unique hash input while rendering nothing visible to the user. This technique is documented in the FAQ (lines 88-106) as the standard method for creating invisible but distinct widgets, particularly useful for icon buttons or spacing elements.
Do built-in widgets like TreeNode affect the ID stack?
Yes. Tree nodes, tabs, and other container widgets automatically call PushID() internally when opened, creating a new scope for all child widgets. This automatic scoping is why widgets with identical labels placed under different tree nodes do not collide, even without manual PushID calls.
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 →