How to Create Custom ImGui Widgets: A Complete Guide to Extending Dear ImGui
Creating custom ImGui widgets requires generating a unique ID with GetID(), defining a bounding rectangle, registering the item with ItemAdd(), handling interaction states via ButtonBehavior(), and rendering visuals using the ImDrawList API.
The Dear ImGui library (ocornut/imgui) provides a modular architecture that allows developers to implement interactive UI elements following the same patterns used by built-in controls. By understanding the internal item system and interaction helpers, you can create custom widgets that fully integrate with ImGui's navigation, focus management, and styling systems.
The Four-Step Pattern for Custom ImGui Widgets
Every widget in Dear ImGui follows a consistent implementation pattern visible throughout imgui_widgets.cpp. This blueprint ensures your custom controls behave identically to native buttons, sliders, and checkboxes.
1. Define an ID and Bounding Rectangle
Every widget requires a unique ImGuiID to identify it within the window's item stack. Call window->GetID(label) to generate a stable identifier based on the current ID stack and label string. Simultaneously, calculate an ImRect describing the widget's screen area using window->DC.CursorPos and ImGui::CalcItemSize().
2. Register with ItemAdd
Call ImGui::ItemAdd(bb, id) to register the widget with ImGui's item system. This function, declared in imgui_internal.h, makes ImGui aware of the item so it participates in focus, hover, and navigation handling. If ItemAdd returns false, the widget is clipped or otherwise inactive, and you should return early. For navigation data, use the overload ItemAdd(bb, id, &nav_legacy).
3. Handle Interaction with ButtonBehavior
The ImGui::ButtonBehavior() function, implemented in imgui_widgets.cpp at line 461, encapsulates mouse, keyboard, and gamepad interaction logic. Pass the bounding box, ID, and output parameters for hovered and held states. The function returns true when the widget is pressed. Customize behavior using flags like ImGuiButtonFlags_Repeat for held-button repeating actions or ImGuiButtonFlags_PressedOnDragDropHold for drag-and-drop support.
4. Render with ImDrawList
After processing interaction, render the widget using window->DrawList->Add... functions. The ImDrawList API provides methods for drawing rectangles, text, lines, and complex shapes. Use ImGui::RenderTextClipped() for text alignment within the bounding box and ImGui::GetColorU32() to respect the current style colors.
Complete Custom Widget Implementation
Here is the canonical implementation pattern used throughout the Dear ImGui source code:
bool MyCustomWidget(const char* label, MyData* data)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems) return false;
// 1. ID & bounding box
ImGuiID id = window->GetID(label);
ImVec2 pos = window->DC.CursorPos;
const ImVec2 size = ImGui::CalcItemSize(ImVec2(0,0), 100, ImGui::GetFrameHeight());
const ImRect bb(pos, pos + size);
// 2. Register the item
if (!ImGui::ItemAdd(bb, id))
return false;
// 3. Interaction logic (hover/press)
bool hovered, held;
bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held,
ImGuiButtonFlags_None);
// 4. Rendering (custom look)
ImU32 col_bg = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive :
hovered ? ImGuiCol_ButtonHovered :
ImGuiCol_Button);
window->DrawList->AddRectFilled(bb.Min, bb.Max, col_bg, ImGui::GetStyle().FrameRounding);
ImGui::RenderTextClipped(bb.Min, bb.Max, label, nullptr, nullptr,
ImVec2(0.5f, 0.5f), nullptr);
return pressed;
}
Practical Custom Widget Examples
These examples demonstrate specific interaction patterns found in imgui_widgets.cpp and imgui_demo.cpp.
Simple Toggle Button
This example combines a standard button with custom coloring to display binary state:
bool MyToggle(const char* label, bool* v)
{
if (ImGui::Button(label)) *v = !*v;
ImGui::SameLine();
ImGui::TextColored(*v ? ImVec4(0,1,0,1) : ImVec4(1,0,0,1), *v ? "ON" : "OFF");
return *v;
}
Custom Color Picker with Draggable Hue Bar
This implementation uses ButtonBehavior to create a draggable hue selector, referencing the pattern used in ImGui's color picker internals:
bool MyColorPicker(const char* label, ImVec4* col)
{
ImGuiWindow* win = ImGui::GetCurrentWindow();
ImGuiID id = win->GetID(label);
ImVec2 p = win->DC.CursorPos;
const float hue_bar_width = 200.0f;
const ImRect bb(p, p + ImVec2(hue_bar_width, ImGui::GetFrameHeight()));
if (!ImGui::ItemAdd(bb, id)) return false;
bool hovered, held;
ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
if (held)
{
float t = (ImGui::GetIO().MousePos.x - bb.Min.x) / hue_bar_width;
t = ImClamp(t, 0.0f, 1.0f);
ImGui::ColorConvertHSVtoRGB(t, 1.0f, 1.0f, col->x, col->y, col->z);
}
// draw hue gradient
win->DrawList->AddRectFilledMultiColor(bb.Min, bb.Max,
ImGui::GetColorU32(ImVec4(1,0,0,1)),
ImGui::GetColorU32(ImVec4(1,1,0,1)),
ImGui::GetColorU32(ImVec4(0,1,0,1)),
ImGui::GetColorU32(ImVec4(0,1,1,1)));
float marker_x = bb.Min.x + hue_bar_width * ImGui::ColorConvertRGBtoHSV(col->x, col->y, col->z, nullptr, nullptr, nullptr);
win->DrawList->AddLine(ImVec2(marker_x, bb.Min.y), ImVec2(marker_x, bb.Max.y),
ImGui::GetColorU32(ImGuiCol_Text), 2.0f);
return held;
}
Drag-and-Drop Target Widget
This example demonstrates integrating with ImGui's drag-and-drop system using ButtonBehavior for hover detection:
bool MyDragDropTarget(const char* label, void* payload)
{
ImGuiWindow* win = ImGui::GetCurrentWindow();
ImGuiID id = win->GetID(label);
ImVec2 p = win->DC.CursorPos;
const ImRect bb(p, p + ImGui::CalcItemSize(ImVec2(0,0), 100, 0));
if (!ImGui::ItemAdd(bb, id)) return false;
bool hovered, held;
ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* pl = ImGui::AcceptDragDropPayload("MY_TYPE")) {
memcpy(payload, pl->Data, pl->DataSize);
ImGui::EndDragDropTarget();
return true;
}
ImGui::EndDragDropTarget();
}
win->DrawList->AddRect(bb.Min, bb.Max,
ImGui::GetColorU32(hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button));
ImGui::RenderTextClipped(bb.Min, bb.Max, label, nullptr, nullptr,
ImVec2(0.5f, 0.5f), nullptr);
return false;
}
Advanced Interaction Techniques
When building complex custom widgets in Dear ImGui, consider these specialized patterns found in the source code:
-
Keyboard and Gamepad Navigation: Pass
ImGuiButtonFlags_NoNavFocusorImGuiButtonFlags_NoNavtoButtonBehavior()to control navigation focus, or setImGuiItemFlags_NoNavviaPushItemFlag()beforeItemAdd(). -
Drag-and-Drop Support: Use
ImGuiButtonFlags_PressedOnDragDropHoldwithButtonBehavior()and checkg.DragDropActiveto handle drag sources and targets properly. -
Repeat Actions: For buttons that should trigger repeatedly while held, pass
ImGuiButtonFlags_RepeattoButtonBehavior()or add theImGuiItemFlags_ButtonRepeatflag to the item. -
Manual ID Management: If you bypass
ItemAdd(), you must callKeepAliveID(id)manually to prevent the ID from being recycled, as noted in the comment at line 5658 ofimgui.cpp. -
ID Conflict Resolution: If your widget uses multiple interactive areas with the same ID base, temporarily disable duplicate-ID checks using
PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true)as referenced inimgui.cpp(lines 631-645).
Essential Source Files for Custom Widget Development
Understanding these key files in the ocornut/imgui repository provides the necessary context for advanced widget development:
-
imgui.h: Contains the public API includingBegin(),End(), andGetID()that form the foundation of widget interaction. -
imgui_internal.h: Declares internal helpers includingItemAdd(),ButtonBehavior()(line 3766), and navigation flags required for custom implementations. -
imgui_widgets.cpp: Houses theButtonBehaviorimplementation (line 461) and serves as the reference for built-in widgets likeButton()andCheckbox(). -
imgui.cpp: Contains core engine logic including theKeepAliveID()comment (line 5658) relevant when bypassingItemAdd(), and ID conflict handling (lines 631-645). -
imgui_demo.cpp: Demonstrates practical widget combinations and higher-level UI patterns.
Summary
- ID Generation: Use
window->GetID()to create stable identifiers that respect ImGui's ID stack. - Item Registration: Always call
ItemAdd()with your bounding box and ID to enable focus, hover, and navigation handling. - Interaction Handling: Leverage
ButtonBehavior()fromimgui_widgets.cppto process mouse, keyboard, and gamepad input consistently. - Rendering: Draw custom visuals using
window->DrawListafter interaction processing to ensure responsive feedback. - Advanced Features: Control navigation, drag-and-drop, and repeat behavior through specific flags passed to
ButtonBehavior()orPushItemFlag().
Frequently Asked Questions
What is the minimum code needed for a custom ImGui widget?
The absolute minimum requires four elements: generate an ID with GetID(), define an ImRect bounding box, register with ItemAdd(bb, id), and return a boolean indicating activation. While you can skip ButtonBehavior() for non-interactive display items, any clickable widget should use it to handle hover and pressed states consistently with the rest of the library.
How do I handle keyboard navigation in custom widgets?
Pass appropriate flags to ButtonBehavior() such as ImGuiButtonFlags_NoNavFocus to prevent focus capture, or use PushItemFlag(ImGuiItemFlags_NoNav) before ItemAdd() to exclude the widget from navigation entirely. For full navigation support, ensure your widget responds to the ImGuiKey_Enter or ImGuiKey_Space inputs that ButtonBehavior() processes automatically when the item is focused.
Can I create custom widgets without including imgui_internal.h?
While basic drawing is possible using only imgui.h, creating interactive widgets that properly handle focus, navigation, and input requires imgui_internal.h. This header provides access to ItemAdd(), ButtonBehavior(), and the ImGuiWindow structure needed to access window->DrawList and window->DC.CursorPos.
How do I avoid ID conflicts when creating multiple instances of my widget?
Dear ImGui automatically handles ID uniqueness through its ID stack mechanism when you use GetID(label). However, if your widget contains multiple interactive sub-elements (like a complex slider with separate drag areas), use PushID() before generating IDs for sub-components, or temporarily allow duplicate IDs with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true) as shown in imgui.cpp lines 631-645.
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 →