How the Dear ImGui ID System Works and How to Avoid ID Collisions
The Dear ImGui ID system generates unique 32-bit ImGuiID values by hashing the current ID stack with a user-supplied seed (string, pointer, or integer), and you can prevent collisions by scoping repeating labels with PushID/PopID or using pointer-based identifiers for dynamic objects.
Every interactive widget in Dear ImGui (ocornut/imgui) requires a unique identifier to track state, handle interactions, and manage persistent storage. The Dear ImGui ID system computes these identifiers as deterministic hashes of the current ID stack combined with user-provided seeds, making it crucial to understand how ID collisions occur when multiple widgets share the same label within the same scope.
How Dear ImGui Generates 32-bit IDs
The core identifier generation logic resides in ImGuiWindow::GetID() within imgui_internal.h (lines 2827-2831). When you call any widget function, ImGui concatenates the current ID stack with your supplied seed and runs it through the ImHashStr function—a Murmur-like 32-bit hash algorithm.
// Simplified view from imgui.cpp
ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) {
ImGuiID seed = ImHashStr(str, str_end ? (int)(str_end - str) : 0, 0);
return ImHashStr(&IDStack[0], IDStack.Size * sizeof(ImGuiID), seed);
}
The ID Stack Mechanism
Each window maintains an ID stack (ImGuiWindow::IDStack) that scopes all subsequent ID calculations. Whenever ImGui enters a new scope via Begin(), BeginChild(), or PushID(), it pushes a hashed seed onto this stack. The final ImGuiID for any widget is the hash of the entire stack contents combined with the widget's specific seed.
Public API for ID Generation
The public interface exposes several overloads declared in imgui.h (lines 610-614) that delegate to ImGuiWindow::GetID:
GetID(const char* str_id)– Uses the string itself as the seed (most common for widget labels)GetID(const void* ptr_id)– Uses the pointer value for stable objects (e.g.,&myStruct)GetID(int int_id)– Uses integer values for small, predictable IDsGetIDFromPos(const ImVec2& p_abs)– Generates IDs based on absolute screen coordinatesGetIDFromRectangle(const ImRect& r_abs)– Creates region-based identifiers from rectangle corners
All overloads eventually route through GetIDWithSeed (imgui_internal.h lines 3470-3471), which allows explicit seed injection used by PushID/PopID pairs.
Common Causes of ID Collisions
ID collisions occur when two widgets generate identical 32-bit hashes within the same window scope. The most common mistake is reusing the same label text inside a loop without scoping:
// WRONG: Both buttons will have the same ID
for (int i = 0; i < 3; i++) {
if (ImGui::Button("Delete")) { /* ... */ } // Collision!
}
Since both buttons compute the same hash value, Dear ImGui treats them as the same widget, causing the second button to be unresponsive and share state with the first.
How to Prevent ID Collisions
Use PushID and PopID for Iterated Content
Wrap dynamic content in PushID/PopID pairs to inject uniqueness into the ID stack. This is the standard approach for lists and grids:
// Example from imgui_demo.cpp - List with unique IDs
for (int i = 0; i < items.Size; ++i) {
ImGui::PushID(i); // Unique seed per row
if (ImGui::Button("Delete")) {
DeleteItem(i);
}
ImGui::SameLine();
ImGui::Text("%s", items[i].c_str());
ImGui::PopID();
}
Prefer Pointer-Based IDs for Dynamic Objects
For heap-allocated or dynamically created widgets, pass the object pointer directly to guarantee uniqueness:
MyWidget* w = GetWidget();
ImGui::SliderFloat("Size", &w->size, 0.1f, 10.0f);
ImGui::Button("Reset", ImGui::GetID(w)); // Unique per object instance
This technique leverages GetID(const void* ptr_id) and ensures that distinct objects never collide regardless of their label text.
Hide Labels While Preserving ID Uniqueness
Use the ## syntax to hide label text from the UI while keeping it part of the ID calculation:
ImGui::Button("##node1"); // Visible text is empty, but ID includes "node1"
ImGui::Button("##node2"); // Distinct ID because seed differs
Avoid Mixing Implicit and Explicit ID Scopes
If you PushID("Menu") and then call Button("Item"), the final ID computes as hash("MenuItem"). Changing the push value will change all child IDs, which is intentional but can cause accidental collisions if the push value is not unique across scopes.
Geometry-Based ID Considerations
When using GetIDFromPos or GetIDFromRectangle, ensure the geometry is unique per item (e.g., include a per-item offset). Two identical rectangles at the same screen location will generate identical IDs, causing interaction conflicts.
Implementation Details
The ID system implementation spans several key files in the ocornut/imgui repository:
imgui_internal.h(lines 2827-2831, 3470-3471) – ContainsImGuiWindow::GetID*overloads andGetIDWithSeedimgui.cpp– ImplementsImHashStr(the Murmur-like hash) and ID stack manipulation logicimgui_widgets.cpp– Demonstrates how widgets callGetIDto obtain their identifiersimgui_demo.cpp– Provides Working examples ofPushID/PopIDusage patterns
The stack-based architecture ensures that IDs remain stable across frames as long as the hierarchy is consistent, enabling Dear ImGui to track widget state persistently without requiring user-managed GUIDs.
Summary
- Dear ImGui IDs are 32-bit hashes computed from the current ID stack combined with a user-supplied string, pointer, or integer seed
- Collisions occur when identical labels appear in the same scope without differentiation, causing widgets to share state and input handling
- Use
PushID/PopIDto isolate loops and dynamic content, or pass explicit pointer/integer IDs toGetID()overloads - The
##suffix hides labels from display while preserving their contribution to the ID hash - Source implementation resides in
imgui_internal.handimgui.cpp, utilizingImHashStrfor deterministic hashing
Frequently Asked Questions
What happens when two Dear ImGui widgets have the same ID?
When two widgets generate identical ImGuiID values within the same window, Dear ImGui treats them as the same underlying widget. The second widget will not respond to interactions independently, and both will share the same state (hover, active, and persistent storage). This manifests as "frozen" buttons or sliders that seem to control each other.
How do I create unique IDs for items in a loop?
Wrap the loop body with ImGui::PushID(i) and ImGui::PopID() where i is your loop index. This pushes the integer onto the ID stack, ensuring that subsequent widgets (like Button("Delete")) compute unique hashes for each iteration. Alternatively, use pointer-based IDs with ImGui::GetID(&your_array[i]) if your data structure provides stable memory addresses.
Can I use the same label text for different buttons?
Yes, but only if they exist in different ID scopes. You can use the same label in different windows, different BeginChild sections, or within different PushID/PopID blocks. Within the same scope, you must differentiation them using the ## suffix (e.g., Button("OK##1") and Button("OK##2")) or by calling PushID before each button.
What is the difference between PushID and GetID?
PushID modifies the current window's ID stack by pushing a new seed, affecting all subsequent widget IDs until PopID is called. GetID simply returns the computed ImGuiID for a given seed without altering the stack state. Use PushID when creating scoped regions with multiple widgets, and use GetID when you need to obtain an ID value for explicit storage or to pass into low-level API functions.
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 →