How to Create Custom Widgets Using `imgui_internal.h`: The Complete Guide
The imgui_internal.h header exposes the low-level item lifecycle system and interaction helpers required to build custom Dear ImGui widgets that fully integrate with navigation, focus, and styling.
Dear ImGui maintains a strict separation between its stable public API (imgui.h) and the low-level internal machinery housed in imgui_internal.h. While the public API provides high-level convenience functions, the internal API grants direct access to the item registration system, behavior helpers, and drawing primitives that the library uses to implement every built-in widget. Mastering these internal hooks allows you to construct bespoke controls that behave identically to native ImGui components.
The Internal API Architecture
Creating a custom widget requires orchestrating four core internal systems. Unlike the public API, which handles these steps automatically, the internal API requires you to explicitly manage the widget's identity, boundaries, interaction state, and rendering.
1. Generate a Stable ID
Every widget needs a unique ImGuiID to track state across frames. Use ImGui::GetID(label) or the lower-level ImHashStr to generate this identifier based on string labels or pointer values.
2. Define the Bounding Box
Construct an ImRect that encapsulates the widget's screen position and dimensions. This rectangle drives clipping, hit-testing, and navigation focus calculations.
3. Register with ItemAdd
Call ItemAdd (imgui_internal.h line 3478, implemented in imgui.cpp) to register the widget with Dear ImGui's current window. This function:
- Stores the item's data in
g.LastItemData - Applies clipping so the widget hides when outside the window bounds
- Registers the item for navigation and focus handling
bool ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb = NULL, ImGuiItemFlags extra_flags = 0);
4. Handle Interaction with Behavior Helpers
Use dedicated behavior functions to compute hover, active, and pressed states. ButtonBehavior (declared around line 3766 in imgui_internal.h) processes mouse and keyboard input, updates navigation flags, and manages the g.NavId and NavActivateId states. Alternative helpers include SliderBehavior and ComboBehavior located in imgui_widgets.cpp.
5. Render via Draw Lists
Retrieve the current window's draw list using ImGui::GetWindowDrawList() and issue primitive commands (AddRectFilled, AddText, AddCircleFilled, etc.). Always pull colors from ImGui::GetStyle().Colors to respect the active theme.
Step-by-Step Implementation Guide
Creating IDs and Bounding Boxes
Begin by checking if the window is skipping items (optimization for clipped content), then calculate your widget's rectangle:
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) return false;
ImGuiID id = ImGui::GetID("MyCustomWidget");
ImVec2 pos = window->DC.CursorPos;
ImVec2 size = ImGui::CalcItemSize(ImVec2(0,0), 100.0f, ImGui::GetFrameHeight());
ImRect bb(pos, pos + size);
Registering the Item
The ItemAdd call is mandatory for navigation and focus support. Without this, your widget will not respond to tab ordering or gamepad input:
if (!ImGui::ItemAdd(bb, id)) return false;
For widgets that don't render every frame (such as drag-and-drop proxies), call KeepAliveID(id) to prevent the ID from being recycled by Dear ImGui's hash table.
Processing Input States
ButtonBehavior is the workhorse for clickable widgets. It outputs hover and held states while internally handling navigation activation:
bool hovered = false, held = false;
ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
bool clicked = held && ImGui::IsMouseReleased(ImGuiMouseButton_Left);
For value-based controls, SliderBehavior (defined in imgui_widgets.cpp) provides拖拽 logic, clamping, and keyboard manipulation:
float new_value = *value;
bool hovered = false;
ImGui::SliderBehavior(bb, id, &new_value, v_min, v_max, ImGuiSliderFlags_None, &hovered);
Complete Code Examples
Example 1: Custom Toggle Button
This implementation demonstrates the full lifecycle: ID generation, item registration, behavior handling, and custom rendering with shape primitives.
bool CustomToggle(const char* label, bool* v)
{
// Step 1: ID and window context
ImGuiID id = ImGui::GetID(label);
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) return false;
// Step 2: Bounding box calculation
ImVec2 pos = window->DC.CursorPos;
ImVec2 size = ImGui::CalcItemSize(ImVec2(0,0), ImGui::GetFontSize() * 2.0f, ImGui::GetFontSize() * 1.2f);
ImRect bb(pos, pos + size);
// Step 3: Register item for navigation/clipping
ImGui::ItemAdd(bb, id);
// Step 4: Interaction handling
bool hovered = false, held = false;
ImGui::ButtonBehavior(bb, id, &hovered, &held, 0);
// Step 5: State mutation
bool pressed = held && ImGui::IsMouseReleased(ImGuiMouseButton_Left);
if (pressed) *v = !*v;
// Step 6: Rendering
ImU32 col_bg = ImGui::GetColorU32(*v ? ImGuiCol_ButtonActive :
held ? ImGuiCol_ButtonActive :
hovered ? ImGuiCol_ButtonHovered :
ImGuiCol_Button);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
draw_list->AddRectFilled(bb.Min, bb.Max, col_bg, window->WindowRounding);
// Draw indicator circle
float pad = bb.GetHeight() * 0.2f;
ImVec2 circle_pos = *v ? ImVec2(bb.Max.x - pad - (bb.GetHeight() * 0.3f), bb.GetCenter().y)
: ImVec2(bb.Min.x + pad + (bb.GetHeight() * 0.3f), bb.GetCenter().y);
draw_list->AddCircleFilled(circle_pos, bb.GetHeight() * 0.3f, IM_COL32(255, 255, 255, 255));
return pressed;
}
Example 2: Custom Slider with Rail Rendering
This example leverages SliderBehavior for complex interaction logic while providing completely custom visuals.
bool CustomSliderFloat(const char* label, float* value, float v_min, float v_max)
{
ImGuiID id = ImGui::GetID(label);
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) return false;
ImVec2 size = ImGui::CalcItemSize(ImVec2(0,0), 200.0f, ImGui::GetFrameHeight());
ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
if (!ImGui::ItemAdd(bb, id)) return false;
// Use internal slider behavior for drag/keyboard logic
bool hovered = false;
float temp_val = *value;
ImGui::SliderBehavior(bb, id, ImGuiDataType_Float, &temp_val, &v_min, &v_max, "", 0, &hovered);
if (temp_val != *value) *value = temp_val;
// Custom rendering: horizontal rail
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImRect rail_bb(bb.Min + ImVec2(0, bb.GetHeight() * 0.4f),
bb.Max - ImVec2(0, bb.GetHeight() * 0.4f));
draw_list->AddRectFilled(rail_bb.Min, rail_bb.Max,
ImGui::GetColorU32(ImGuiCol_FrameBg), 4.0f);
// Custom rendering: position indicator
float t = (*value - v_min) / (v_max - v_min);
float grab_x = ImLerp(rail_bb.Min.x, rail_bb.Max.x, t);
ImVec2 grab_center(grab_x, rail_bb.GetCenter().y);
draw_list->AddCircleFilled(grab_center, bb.GetHeight() * 0.4f,
ImGui::GetColorU32(hovered ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab));
// Label rendering
ImGui::RenderTextClipped(bb.Min, bb.Max, label, NULL, NULL, ImVec2(0.5f, 0.5f));
return hovered;
}
Key Source Files to Reference
Understanding the internal implementation requires studying these specific files in the ocornut/imgui repository:
imgui_internal.h– DeclaresItemAdd,ButtonBehavior,KeepAliveID, and the navigation flag constants (e.g.,ImGuiButtonFlags_NoNavFocus).imgui.cpp– Contains the implementations ofItemAddand the core item lifecycle logic that managesg.LastItemDataand clipping.imgui_widgets.cpp– Houses the concrete behavior implementations includingSliderBehavior,ComboBehavior, and the detailed logic forButtonBehavior.imgui_draw.cpp– Provides theImDrawListprimitives (AddRect,AddText, etc.) used for custom rendering.imgui_demo.cpp– Contains practical demonstrations of advanced rendering techniques and examples of how the internal API constructs complex widgets.
Summary
- The internal API in
imgui_internal.hexposes the building blocks used by Dear ImGui's own widget suite, enabling fully native custom controls. - ItemAdd is mandatory for navigation, focus, and clipping support; it registers your widget's bounding box and ID with the current window context.
- ButtonBehavior and SliderBehavior handle complex input processing, navigation activation, and state management automatically.
- KeepAliveID prevents ID collision for widgets that skip frames or exist only during specific interaction states.
- Custom widgets should draw using
ImDrawListprimitives and referenceImGui::GetStyle().Colorsto maintain visual consistency with the user's theme.
Frequently Asked Questions
When should I use imgui_internal.h instead of the public API?
Use the internal API when you need custom geometry or specialized interaction patterns that the standard Button(), Slider(), or InputText() functions cannot accommodate. The internal API is essential for widgets requiring non-rectangular hit areas, composite controls, or unique rendering pipelines. Note that because these symbols are not guaranteed stable across versions, you should pin your project to a specific Dear ImGui version when using internals.
What is the difference between ItemAdd and ButtonBehavior?
ItemAdd registers the widget's existence with Dear ImGui's window and navigation systems, handling clipping and focus allocation. ButtonBehavior processes input events (mouse clicks, keyboard navigation, gamepad) and calculates hover/active states. You must call ItemAdd before ButtonBehavior to establish the widget's identity and bounding box, but ButtonBehavior is only required if your widget needs click or hover detection.
Do custom widgets built with the internal API support gamepad navigation?
Yes. Because ItemAdd populates the internal LastItemData structure and ButtonBehavior respects the NavId system, widgets built using these primitives automatically participate in Dear ImGui's keyboard and gamepad navigation. The behavior functions internally set g.NavActivateId and handle ImGuiButtonFlags_NoNavFocus flags, ensuring your custom widget responds identically to built-in controls when using tab navigation or directional pads.
How do I handle text input inside a custom widget?
For widgets requiring text entry, use InputTextEx (declared in imgui_internal.h and implemented in imgui_widgets.cpp) rather than building raw input handling. This internal function exposes the full text editing state machine, undo/redo buffers, and callback system while allowing you to control the rendering. Alternatively, call ImGui::InputText() publicly and overlay custom graphics using GetWindowDrawList() if you only need visual customization rather than behavioral changes.
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 →