How to Implement Tree Nodes and Collapsible Headers in ImGui
Dear ImGui provides the TreeNode(), TreeNodeEx(), and CollapsingHeader() APIs that leverage the internal TreeNodeBehavior() function to render hierarchical, collapsible UI elements with automatic state persistence.
The ocornut/imgui library offers a robust set of tools for creating hierarchical interfaces. Whether you are building property panels, scene graphs, or settings menus, understanding how to implement tree nodes and collapsible headers in ImGui is essential for organizing complex UI layouts. These widgets share a unified architecture that handles input, navigation, and persistence through a minimal set of high-level functions.
Core Architecture Behind Tree Nodes
TreeNodeBehavior() – The Internal Engine
At the heart of every tree widget lies TreeNodeBehavior(), defined in imgui_internal.h (line 3770). This internal function manages input detection, state tracking for open/close transitions, arrow rendering, and navigation integration. It serves as the single implementation point that powers all public tree APIs.
Public API Wrappers
The user-facing functions TreeNode() and TreeNodeEx() (declared in imgui.h, lines 743-750) are thin wrappers that generate unique ImGuiID values, process flag bitmasks, and invoke TreeNodeBehavior(). When a node returns true indicating it is open, these functions automatically push the ID onto the internal stack, requiring a matching TreePop() to restore hierarchy.
CollapsingHeader() as a Convenience Wrapper
CollapsingHeader() (lines 757-758 in imgui.h) provides a simplified interface for header-style nodes. Unlike TreeNode(), it forces the framed style and disables automatic TreePush(), meaning it returns the open state without requiring TreePop().
Configuring Appearance with ImGuiTreeNodeFlags
The ImGuiTreeNodeFlags_ enumeration (lines 1304-1336 in imgui.h) controls visual styling and interaction behavior. Key flags include:
ImGuiTreeNodeFlags_Framed: Renders the node with a full background frameImGuiTreeNodeFlags_Leaf: Removes the expand arrow for terminal nodesImGuiTreeNodeFlags_DrawLinesFull: Draws connecting lines between hierarchy levelsImGuiTreeNodeFlags_OpenOnDoubleClick: Changes the default single-click to double-click for opening
Implementing Hierarchical Trees in Practice
When building nested structures, remember that TreeNode* calls returning true indicate an open state and begin a new scope. You must close this scope with TreePop().
// Basic tree structure with nested nodes
if (ImGui::TreeNode("Root")) // Opens new ID scope
{
ImGui::Text("Root content");
// Leaf node - no arrow but still requires TreePop()
if (ImGui::TreeNodeEx("Leaf Item", ImGuiTreeNodeFlags_Leaf))
{
ImGui::Text("Leaf content");
ImGui::TreePop();
}
// Nested child
if (ImGui::TreeNode("Child Node"))
{
ImGui::Text("Child content");
ImGui::TreePop();
}
ImGui::TreePop(); // Close "Root"
}
For framed header-style sections that do not affect indentation:
// CollapsingHeader requires no TreePop()
if (ImGui::CollapsingHeader("Settings", ImGuiTreeNodeFlags_Framed))
{
ImGui::Checkbox("Enable feature", &feature_enabled);
ImGui::SliderFloat("Speed", &speed, 0.0f, 1.0f);
}
To render connecting lines between hierarchy levels:
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DrawLinesFull |
ImGuiTreeNodeFlags_Framed;
if (ImGui::TreeNodeEx("With lines", flags))
{
ImGui::Text("Child with visual connection");
ImGui::TreePop();
}
State Management and Navigation
Open/close states persist automatically via internal storage keyed to the widget's ImGuiID. The ImGuiTreeNodeStackData structure (lines 1424-1431 in imgui_internal.h) tracks this state within ImGuiContext::TreeNodeStack.
To programmatically control visibility, use SetNextItemOpen() before the tree node call. The GetTreeNodeToLabelSpacing() utility (line 756 in imgui.h) provides the standard horizontal offset used for aligning custom content with tree labels.
Summary
TreeNodeBehavior()inimgui_internal.hserves as the single implementation for all tree widgetsTreeNode()andTreeNodeEx()requireTreePop()when they return true to close the scopeCollapsingHeader()automatically handles cleanup and returns the open state without requiringTreePop()- Use
ImGuiTreeNodeFlagsto customize appearance with frames, bullets, or connecting lines - State persistence is automatic via internal ID storage, or can be forced with
SetNextItemOpen()
Frequently Asked Questions
What is the difference between TreeNode and CollapsingHeader?
TreeNode() pushes the current ID onto the stack when open and requires TreePop() to close the scope, affecting indentation and hierarchy. CollapsingHeader() is a convenience wrapper that forces a framed style and manages the scope internally, returning only the boolean open state without requiring TreePop().
How do I persist the open/close state of tree nodes?
Dear ImGui automatically persists tree node states using the widget's ImGuiID as a key in internal storage. To force a specific state programmatically, call SetNextItemOpen(true) or SetNextItemOpen(false) immediately before the TreeNode() call.
Why must I call TreePop() after TreeNode()?
When TreeNode() returns true, it pushes a new identifier onto the ID stack to isolate child elements. Calling TreePop() restores the previous ID scope and indentation level. Failure to call TreePop() results in an ID stack mismatch and assertion errors.
How do I draw connecting lines between tree nodes?
Enable the ImGuiTreeNodeFlags_DrawLinesFull or ImGuiTreeNodeFlags_DrawLinesToNodes flags when calling TreeNodeEx(). These flags trigger the internal line-drawing logic within TreeNodeBehavior() to render visual connections between hierarchy levels.
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 →