How the ImGui ID Stack System Works: PushID, PopID, and the ## Operator
TLDR: ImGui uses a 32-bit ID stack where PushID() layers identifiers and PopID() removes them, combining hashes with the ## label suffix to generate unique widget IDs without restrictive naming conventions.
The Dear ImGui library (ocornut/imgui) identifies every interactive element through a sophisticated ID stack system that resolves label collisions deterministically. Understanding PushID, PopID, and the ## operator is essential for building complex UIs with duplicate labels or dynamically generated content.
What Is the ImGui ID Stack?
Every widget (buttons, sliders, trees) requires a unique 32-bit identifier. Rather than forcing globally unique labels, ImGui maintains a per-window stack in ImGuiWindow::IDStack that combines multiple hash layers. When a widget requests an ID via ImGui::GetID(), the system walks the current stack, mixes the hashes together, and produces a final unique identifier. The stack implementation resides in imgui.cpp around line 9334.
PushID and PopID: Core API
The stack manipulation functions provide four distinct ways to push identifiers onto the stack before removal with PopID().
The Four PushID Overloads
| Overload | Parameter | Purpose |
|---|---|---|
PushID(const char* str_id) |
C-string | Stable string identifier (e.g., "MyButton") |
PushID(const char* str_id_begin, const char* str_id_end) |
Pointer range | Substring or custom-terminated names |
PushID(const void* ptr_id) |
Pointer | Memory address as unique key (e.g., object pointer) |
PushID(int int_id) |
Integer | Numeric index (e.g., loop counter) |
All overloads resolve to an internal PushID(id_hash) call that pushes the 32-bit hash onto the stack, as implemented in imgui.cpp lines 9342–9358.
The ## Operator and Label Hashing
The ## token acts as a label delimiter recognized by GetID(). Text before ## renders visibly; text after provides additional hashing material but remains hidden from the user.
if (ImGui::Button("Save##file1")) {
// Visible: "Save", ID: hash("Save##file1")
}
Combining PushID with Empty Labels
For dynamic content, combine PushID with an empty label to avoid visible suffixes entirely:
ImGui::PushID(item_id); // item_id = unique integer or pointer
ImGui::Button("##"); // Uses only the stack's hash for ID
ImGui::PopID();
This pattern appears throughout imgui_widgets.cpp (lines 3832–3834).
Practical Usage Patterns
Disambiguating Identical Labels
When multiple "OK" buttons exist, wrap each in a scoped ID to prevent them from sharing the same identifier:
for (int i = 0; i < 5; i++) {
ImGui::PushID(i);
if (ImGui::Button("OK")) { /* handles i-th button */ }
ImGui::PopID();
}
Stable IDs for Dynamic Content
Use immutable pointers (e.g., object addresses) rather than array indices to ensure focus and activation state persist through reordering or deletion. This prevents input focus from jumping to the wrong item when the list changes.
Nested Scopes
The stack supports arbitrary nesting for hierarchical IDs:
ImGui::PushID("Parent");
ImGui::PushID(child_index);
ImGui::Button("Action");
// ID = hash("Parent") ^ hash(child_index) ^ hash("Action")
ImGui::PopID();
ImGui::PopID();
Safety and Debugging
ImGui asserts balanced push/pop operations at runtime. A mismatch triggers IM_ASSERT_USER_ERROR in imgui_tables.cpp (line 1593), preventing stack corruption that would cause subsequent widgets to receive incorrect IDs.
Summary
- The ID stack in
ImGuiWindow::IDStackcombines 32-bit hashes to create unique widget identifiers. PushIDoffers four overloads (string, range, pointer, int) to layer identifiers, whilePopIDremoves them.- The
##operator hides text from the UI while using it for ID generation. - Empty labels (
"##") combined withPushIDprovide clean visible text with unique underlying IDs. - The system enforces balanced push/pop through assertions to maintain stack integrity.
Frequently Asked Questions
What happens if I forget to call PopID?
If pushes and pops are unbalanced, ImGui triggers an assertion via IM_ASSERT_USER_ERROR (see imgui_tables.cpp line 1593). The stack corruption would cause subsequent widgets to receive incorrect IDs, potentially breaking input handling and focus management.
Can I use the same label for multiple buttons?
Yes, but only if you scope each with PushID or use the ## suffix to differentiate them. Without disambiguation, widgets with identical labels share the same ID and behave as a single logical entity.
Why use PushID instead of ## suffixes?
PushID keeps visible labels clean and readable while providing uniqueness programmatically. It is essential for dynamic lists where generating unique string suffixes would be cumbersome, and it allows using stable pointers or integers that survive reordering better than generated strings.
How does ImGui combine stack hashes?
Each PushID computes a 32-bit hash of its argument and pushes it onto the stack. When GetID processes a label, it mixes the current stack hash with the label's hash (or an empty string if the label is "") to produce the final identifier.
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 →