How ImGui's ID Stack System Works: PushID, PopID, and the ## Suffix
ImGui generates unique 32-bit widget identifiers by maintaining a hierarchical hash stack that combines PushID scopes with label text, while the ## suffix allows hidden identifier tokens in display strings.
The Dear ImGui library (ocornut/imgui) identifies every interactive element—buttons, sliders, trees—through a sophisticated ImGui ID stack mechanism that prevents naming collisions while keeping UI code readable. This system relies on hierarchical hashing stored in ImGuiWindow::IDStack to produce stable identifiers at runtime, combining explicit scope pushes with implicit label hashing.
Core Concept of the ID Stack
ImGui assigns every widget a 32-bit ID derived from a stack of hash values. Because many widgets share identical visible labels (e.g., multiple "OK" buttons), the library builds a stack of identifiers that are combined through hashing to produce a unique final value for each element.
The stack implementation resides in imgui.cpp within the ImGuiWindow structure, specifically the IDStack member. When you call ImGui::PushID(), the function computes a hash of the provided argument and pushes it onto this stack around line 9334. ImGui::PopID() simply removes the topmost entry. When a widget requests an ID via GetID(), ImGui walks the current stack, mixes the hashes together, and combines the result with the widget's label hash to generate the final identifier. This scope is automatically cleared when the window closes.
PushID Overloads and Implementation
ImGui provides four distinct overloads for PushID, each accepting different identifier types to accommodate various programming patterns:
PushID(const char* str_id): Accepts a C-string for stable string-based scoping (e.g.,"MyButton").PushID(const char* str_id_begin, const char* str_id_end): Accepts a pointer range for substrings or custom terminators.PushID(const void* ptr_id): Uses a memory address (such as an object pointer) as a unique key.PushID(int int_id): Accepts a numeric index, commonly used for loop counters.
All overloads are thin wrappers that ultimately call the internal PushID(ImU32 id_hash) function, as seen in imgui.cpp lines 9342–9358. This internal function pushes the pre-computed 32-bit hash onto ImGuiWindow::IDStack, ensuring consistent hashing behavior regardless of input type.
The ## Suffix Operator
The ## token acts as a label delimiter recognized by ImGui::GetID(). Everything following ## is stripped from the visible UI text but fully contributes to the hash calculation. This allows identical display text with distinct identifiers:
if (ImGui::Button("Save##file1")) { /* ... */ } // Visible: "Save", ID: hash("Save##file1")
if (ImGui::Button("Save##file2")) { /* ... */ } // Visible: "Save", ID: hash("Save##file2")
The mechanism processes the string once, splitting the display portion from the identifier portion before hashing. This operates independently of the ID stack but combines with it when both are present.
Practical Patterns and Empty Labels
For dynamic content where labels must remain clean but uniqueness is required, combine PushID with empty labels:
ImGui::PushID(item_ptr); // Push pointer or unique index
ImGui::Button("##"); // Empty label uses only the stack's hash
ImGui::PopID();
This pattern appears throughout the codebase, specifically in imgui_widgets.cpp lines 3832–3834, where internal widgets require invisible identifiers. When nesting scopes, the hashes accumulate hierarchically:
ImGui::PushID("Parent");
ImGui::PushID(child_index);
ImGui::Button("Action"); // Final ID = hash("Parent") ^ hash(child_index) ^ hash("Action")
ImGui::PopID();
ImGui::PopID();
Use immutable keys (like object pointers) rather than array indices when items can be reordered or deleted; this ensures that focus and activation states follow the logical item rather than its position.
Safety Checks and Debugging
ImGui asserts that PushID and PopID calls are balanced. A mismatch triggers a runtime assertion via IM_ASSERT_USER_ERROR, as implemented in imgui_tables.cpp line 1593. This safety check prevents stack corruption that would otherwise cause ID collisions or memory errors. When debugging, verify that every push has a corresponding pop within the same frame and window scope.
Summary
-
ImGui ID stack: A per-window stack (
ImGuiWindow::IDStack) storing 32-bit hash values that scope widget identifiers. -
PushID/PushID: Four overloads (string, string range, pointer, integer) that hash inputs and push onto the stack, implemented in
imgui.cpparound line 9334. -
## suffix: A label delimiter that hides text from display while including it in the ID hash calculation.
-
Empty labels: Combining
PushIDwith"##"creates clean UI elements with stable, unique identifiers derived solely from the stack. -
Safety: Unbalanced push/pop operations trigger assertions (
imgui_tables.cpp:1593) to prevent stack corruption.
Frequently Asked Questions
What is the ImGui ID stack used for?
The ImGui ID stack disambiguates widgets that share identical visible labels by combining hierarchical hash scopes with label text. It allows multiple buttons labeled "OK" to exist simultaneously without ID collisions, and ensures that focus states persist correctly when UI elements are reordered or deleted.
How does the ## suffix work in ImGui labels?
The ## operator splits the label string at runtime: text before ## renders visibly, while text after ## is hidden from display but fully included in the hash calculation. This allows developers to create human-readable labels like "Save" while appending unique tokens (e.g., "##config1") to generate distinct 32-bit IDs.
What happens if I forget to call PopID?
Forgetting PopID creates an unbalanced stack that persists for the remainder of the window's lifetime, causing all subsequent widgets to inherit incorrect hash prefixes. ImGui detects this condition via IM_ASSERT_USER_ERROR in imgui_tables.cpp (line 1593), triggering a runtime assertion in debug builds to alert you of the mismatch.
Can I use pointers as IDs in PushID?
Yes, the PushID(const void* ptr_id) overload accepts memory addresses as unique identifiers. This is the preferred method for stable identification of dynamic objects, as object pointers remain constant even if array indices change, ensuring that ImGui's internal state (focus, scrolling, selection) remains attached to the correct logical item.
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 →