How to Extend Dear ImGui with Custom Widgets: A Complete Guide to the Internal API
To extend Dear ImGui with custom widgets, you generate a unique ID with GetID(), register the bounding box with ItemAdd(), handle interactions via ButtonBehavior(), and render using ImDrawList primitives—all without modifying the core library.
Dear ImGui's immediate-mode architecture in the ocornut/imgui repository separates layout/interaction from rendering, allowing you to create sophisticated custom controls by composing a small set of internal API functions. By following the same pattern used by built-in buttons and sliders, you can add custom drawing that respects clipping rectangles, style colors, and navigation focus.
The Six Essential Steps for Custom Widgets
Every widget in Dear ImGui follows a strict lifecycle. When building custom widgets in your own codebase, implement these six sequential operations:
1. Generate a Stable ID
Before processing or drawing, you must generate a unique identifier so ImGui can track state (hover, active, focus) across frames. Call window->GetID(label) or use ImGui::GetID() directly. According to the source in imgui.cpp, this hashes the ID stack to create a stable 32-bit identifier.
2. Define the Geometry with ItemAdd
Submit the widget’s bounding rectangle (ImRect) to the layout system using ItemAdd(bb, id) defined in imgui.cpp around line 11455. This function registers the rectangle for clipping, hit-testing, and navigation focus. Always call ItemSize(bb) immediately before ItemAdd() to advance the cursor position.
3. Handle Input via ButtonBehavior
Process mouse and keyboard navigation using ButtonBehavior(bb, id, &hovered, &held, flags) from imgui_widgets.cpp (line 545). This implements the generic "mouse-over / mouse-pressed" state machine used by most built-in widgets, handling edge cases like repeat rates and navigation activation automatically.
4. Maintain ID Lifetime with KeepAliveID
If you create a widget that bypasses ItemAdd()—for example, issuing raw draw calls that must still react to IsItemHovered() later—you must call KeepAliveID(id) from imgui.cpp (line 5668). Most custom widgets follow the standard ItemAdd path, so this step is rarely required.
5. Render to ImDrawList
Issue draw commands through the immediate-mode draw list using ImGui::GetWindowDrawList()->Add... primitives. The ImDrawList API (defined in imgui_draw.cpp) provides rectangles, lines, circles, and text that integrate seamlessly with the current theme and clipping.
6. Wrap in a Public Function
Expose your widget as a standard C++ function matching ImGui’s naming conventions (e.g., MyToggle(const char* label, bool* v)). Place this function in your own source files—no modifications to the library are necessary.
Complete Implementation: Custom Toggle Switch
The following implementation demonstrates the complete workflow, creating a functional toggle switch that uses ButtonBehavior, ItemAdd, and ImDrawList:
bool MyToggle(const char* label, bool* v)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
return false;
// 1. Generate unique ID
const ImGuiID id = window->GetID(label);
// 2. Define geometry
const ImVec2 widgetSize = ImVec2(30, 18);
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + widgetSize);
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
// 3. Handle input
bool hovered, held;
ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
if (held && ImGui::IsMouseClicked(0))
*v = !*v;
// 4. Render via ImDrawList
ImU32 col_bg = ImGui::GetColorU32(*v ? ImGuiCol_ButtonActive : ImGuiCol_Button);
ImU32 col_knob = ImGui::GetColorU32(ImGuiCol_Text);
ImDrawList* draw = ImGui::GetWindowDrawList();
draw->AddRectFilled(bb.Min, bb.Max, col_bg, 4.0f);
ImVec2 knobPos = *v ? bb.Max - ImVec2(4, 4) : bb.Min + ImVec2(4, 4);
draw->AddCircleFilled(knobPos, 6.0f, col_knob);
// Optional label rendering
if (hovered && ImGui::IsItemHovered())
ImGui::SetTooltip("%s", label);
return true;
}
This pattern—GetID → ItemSize → ItemAdd → ButtonBehavior → Render—mirrors the implementation of native widgets in imgui_widgets.cpp around line 541.
Advanced Example: Custom Color Picker
For widgets requiring complex hit-testing, compute interaction manually while still using ButtonBehavior for the base interaction state:
bool ColorPicker(const char* label, ImVec4* col)
{
ImGuiWindow* win = ImGui::GetCurrentWindow();
if (win->SkipItems)
return false;
const ImGuiID id = win->GetID(label);
const ImVec2 size = ImVec2(200, 200);
const ImRect bb(win->DC.CursorPos, win->DC.CursorPos + size);
ImGui::ItemSize(bb);
if (!ImGui::ItemAdd(bb, id))
return false;
// Handle interaction
bool hovered, held;
ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
if (held && ImGui::GetIO().MouseClicked[0])
{
ImVec2 mouse = ImGui::GetIO().MousePos - bb.Min;
mouse.x = ImClamp(mouse.x / size.x, 0.0f, 1.0f);
mouse.y = ImClamp(mouse.y / size.y, 0.0f, 1.0f);
// Convert normalized coordinates to RGB
ImGui::ColorConvertHSVtoRGB(mouse.x, 1.0f - mouse.y, col->w,
col->x, col->y, col->z);
}
// Render gradient background
ImDrawList* draw = ImGui::GetWindowDrawList();
for (int i = 0; i < 255; ++i)
{
float t = i / 255.0f;
ImU32 color = ImGui::ColorConvertFloat4ToU32(
ImVec4(t, 1.0f - t, col->z, 1.0f));
draw->AddLine(bb.Min + ImVec2(t * size.x, 0),
bb.Min + ImVec2(t * size.x, size.y), color);
}
// Selection marker
ImVec2 marker = bb.Min + ImVec2(col->x * size.x, (1.0f - col->y) * size.y);
draw->AddCircleFilled(marker, 5.0f, ImGui::GetColorU32(ImGuiCol_CheckMark));
return held;
}
Testing Your Widget in the Demo
To verify your custom widget integrates correctly with clipping and focus systems, add it to imgui_demo.cpp inside the ShowDemoWindow() function:
if (ImGui::CollapsingHeader("Custom Widgets"))
{
static bool toggle = false;
MyToggle("Example Toggle", &toggle);
static ImVec4 color = ImVec4(0.4f, 0.6f, 0.9f, 1.0f);
ColorPicker("Custom Picker", &color);
}
The demo file already contains reference implementations of complex widgets, making it an ideal environment for testing custom interactions without writing a separate application.
Key Source Files for Widget Development
Understanding these core files helps you trace the data flow from ID generation to rendering:
imgui.cpp– ContainsItemAdd(line 11455),KeepAliveID(line 5668), and the navigation handling logic.imgui_widgets.cpp– Implements built-in widgets and exposesButtonBehavior(line 545) for reuse in custom controls.imgui_draw.cpp– DefinesImDrawListand all immediate-mode rendering primitives.imgui_demo.cpp– Reference implementations and test cases; add your widgets here to experiment.
Summary
- Generate IDs using
window->GetID()to maintain state across frames. - Register geometry with
ItemSize()followed byItemAdd()to participate in layout and clipping. - Handle interaction through
ButtonBehavior()to reuse ImGui’s robust input state machine. - Render using
ImDrawListprimitives for automatic style and clipping integration. - Place code in your own source files—modifying
ocornut/imguiis unnecessary for custom widgets. - Reference
imgui_widgets.cppline 541 for the canonical button implementation pattern.
Frequently Asked Questions
Do I need to modify the Dear ImGui source code to create custom widgets?
No. The most common approach is user-side extension—simply adding a C++ function like MyToggle() to your own source files. You only need to modify the core library if you require deep integration with the navigation focus stack or need to add new data types to ImGuiDataType.
What is the difference between ItemSize and ItemAdd?
ItemSize() advances the cursor position and calculates layout bounds, while ItemAdd() (defined in imgui.cpp) actually registers the item with the window’s item list, enabling clipping, hit-testing, and ID tracking. You must call both for every interactive widget.
How do I handle keyboard navigation in custom widgets?
Calling ButtonBehavior() automatically integrates with ImGui’s navigation system. When users navigate with keyboard/gamepad, ImGui will trigger the held output parameter and activate the widget appropriately. For custom navigation behaviors, query ImGui::IsItemFocused() after calling ItemAdd().
Why would I need KeepAliveID?
KeepAliveID() (from imgui.cpp line 5668) is required only when you create a widget that issues raw draw calls without calling ItemAdd(). It prevents the widget’s ID from being garbage-collected when the item is clipped or skipped. Most custom widgets use the standard ItemAdd() path and never need this function.
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 →