How Dear ImGui's ID Stack System Works to Prevent Collisions
Dear ImGui computes unique widget identifiers by hashing the current ID stack—a dynamically managed list of scopes pushed via PushID() and popped via PopID()—combined with the widget's label, ensuring no two interactive elements collide even when labels overlap.
The immediate mode GUI paradigm used by Dear ImGui requires a robust mechanism to track widget state across frames. The library solves this through a hierarchical ID stack system that lives inside ImGuiContext, automatically generating unique hashes by combining scope identifiers with user-provided labels. Understanding how this system operates is essential for debugging focus issues and managing complex nested interfaces in the ocornut/imgui codebase.
Understanding the ID Stack Architecture
The ID stack is a dynamic array stored within ImGuiContext that maintains the current hierarchical scope. Rather than storing explicit identifiers, Dear ImGui computes them on-the-fly by hashing the contents of this stack.
Automatic Scope Management
When you call functions that create new scopes—such as ImGui::Begin() or ImGui::TreeNode()—the library internally executes PushID() for you. The stack is subsequently cleared with PopID() when the scope ends (for example, when matching ImGui::End() is called). This automatic management ensures that widgets nested inside different windows or tree nodes receive distinct identifiers without manual intervention. The core implementation of this mechanism resides in the ID STACK section of imgui.cpp around line 83.
PushID Overloads and Value Types
You can manually influence the ID stack using the PushID function, which offers four overloads defined in imgui.cpp between lines 9355 and 9379. These allow you to push three distinct value types onto the stack:
PushID(const char* str_id)– Hashes a null-terminated stringPushID(const char* str_begin, const char* str_end)– Hashes a string rangePushID(const void* ptr_id)– Uses a pointer value (useful for object-based identification)PushID(int int_id)– Uses an integer value (common for array indices)
The value you push becomes the next element in the stack. The final ID for any widget is the hash of all elements currently on the stack combined with the widget's own label string.
Label Syntax and ID Manipulation
Dear ImGui's label syntax provides two special operators that modify how the ID hash is constructed without changing the visible text:
-
##(double hash) – Adds a hidden suffix to the string that participates in the hash calculation but is not rendered to the screen. This allows multiple buttons with the same visible label to coexist by appending unique identifiers after##. -
###(triple hash) – Excludes everything preceding it from the hash calculation, using only the text that follows. This lets you change the visible text dynamically while maintaining a stable ID for state persistence.
The canonical documentation for these operators appears in docs/FAQ.md under the section About the ID Stack system.
How GetID Generates the Final Hash
Internally, Dear ImGui calls GetID() (defined near the ID STACK section in imgui.cpp) to compute the final identifier. This function iterates over the current stack, mixing each element into a 32-bit hash using the same algorithm employed for string hashing. The resulting hash is stored in the widget's ID field and serves as the key for all state look-ups, including focus, activation, and hover detection.
Practical Implementation Examples
The following patterns demonstrate how to leverage the ID stack system in production code:
Integer-Based Loop Scoping
ImGui::Begin("MyWindow");
for (int i = 0; i < 3; ++i) {
ImGui::PushID(i); // Stack: ["MyWindow", i]
ImGui::Button("Click"); // ID = hash("MyWindow", i, "Click")
ImGui::PopID();
}
ImGui::End();
Pointer-Based Object Identification
MyObject* obj = GetSelectedObject();
ImGui::PushID(obj); // Stack: ["MyWindow", obj]
ImGui::Checkbox("Enabled", &obj->enabled);
ImGui::PopID();
Hidden Suffix for Duplicate Labels
// Same visible text, different IDs
ImGui::Button("Play##song42"); // Stack: ["MyWindow"]; ID = hash("MyWindow", "Play##song42")
ImGui::Button("Play##song43"); // Different ID due to hidden suffix
Dynamic Labels with Stable IDs
// Visible text changes, but ID remains constant
float fps = ImGui::GetIO().Framerate;
char buf[64];
sprintf(buf, "FPS: %.1f###FPSLabel", fps);
ImGui::Text(buf); // ID always equals hash("MyWindow", "FPSLabel")
Debugging ID Collisions
Dear ImGui provides a visual debugging tool to inspect the ID stack hierarchy. You can access it programmatically via ImGui::ShowIDStackToolWindow() or through the demo interface at Demo > Tools > ID Stack Tool. This utility displays every level of the current stack along with intermediate hash values, making it invaluable for troubleshooting why two widgets might be sharing state or why focus is behaving unexpectedly.
Summary
- The ID stack lives within
ImGuiContextand automatically scopes widget identity throughPushIDandPopIDpairs. PushIDsupports string, pointer, and integer overloads located inimgui.cppat lines 9355-9379.- Label syntax uses
##for hidden hash suffixes and###to exclude preceding text from the hash calculation. GetIDiterates the entire stack to produce a 32-bit hash that serves as the widget's unique identifier.- Use
ShowIDStackToolWindow()fromimgui_demo.cppto visualize stack levels and debug collision issues.
Frequently Asked Questions
What happens if I forget to call PopID?
Forgetting to call PopID() creates a stack imbalance that persists until the end of the frame. In development builds, Dear ImGui will assert when the window or scope ends if the stack depth doesn't match the expected value. Unbalanced stacks cause subsequent widgets to inherit incorrect scope prefixes, leading to ID collisions where unrelated widgets share the same state and input handling.
Can I use the same label for multiple buttons in different windows?
Yes. The ImGui::Begin() function automatically pushes the window identifier onto the ID stack, ensuring that identical labels in different windows produce distinct hashes. This hierarchical scoping means you rarely need to manually manage IDs unless creating multiple identical widgets within the same container.
How do I debug ID collisions in Dear ImGui?
Enable the ID Stack Tool by calling ImGui::ShowIDStackToolWindow() or navigating to Demo > Tools > ID Stack Tool in the demo window. This tool displays the current stack hierarchy and the resulting hash values for each level, allowing you to see exactly why two widgets are colliding and which PushID calls are contributing to their final identifiers.
What is the difference between ## and ### in labels?
The ## operator adds hidden text that participates in the hash but is not rendered, allowing unique IDs while maintaining identical visible labels. The ### operator excludes everything preceding it from the hash calculation, using only the text after the triple hash as the identifier. Use ### when you want to change the visible text dynamically (such as displaying changing values) while keeping the ID stable for state persistence.
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 →