# How to Add Custom Widgets in Dear ImGui: A Complete Guide to the Core API

> Learn to add custom widgets in Dear ImGui using GetID ItemAdd and ButtonBehavior. Master the core API for seamless integration and enhanced UI development.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: how-to-guide
- Published: 2026-07-28

---

**Custom widgets in Dear ImGui are constructed using three essential primitives—`GetID()` for unique identification, `ItemAdd()` for layout registration, and `ButtonBehavior()` for input handling—which integrate seamlessly with the library's navigation and rendering pipeline.**

Dear ImGui (ocornut/imgui) exposes its internal widget construction API, allowing developers to create custom interactive elements that behave identically to built-in controls. When you add custom widgets in Dear ImGui using these low-level functions, you inherit automatic support for hover detection, focus management, clipping, and keyboard navigation without additional boilerplate.

## Core Architecture for Custom Widgets

Every widget in Dear ImGui, whether built-in or custom, follows the same architectural pattern involving identity, registration, interaction, and rendering.

**ID Generation** is the foundation. Every widget must own a unique `ImGuiID`, created via `GetID()` or `GetIDWithSeed()`. If your widget contains overlapping interactive regions that temporarily share identifiers, wrap those calls with `PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true)` to prevent conflicts. The ID system is defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h).

**Item Registration** occurs through `ItemAdd()` and `ItemSize()`. Call `ItemSize()` first to reserve space in the layout, then `ItemAdd(bb, id)`—where `bb` is an `ImRect` bounding box—to register the widget with the window's draw list and navigation system. This step, implemented in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp), makes the widget visible to ImGui's internal state tracking.

**Interaction Handling** relies on `ButtonBehavior()`, the low-level engine for hover, held, and pressed states. Located in [`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp), this function accepts an `ImRect`, an `ImGuiID`, and output pointers for hover and held states. Most built-in widgets are thin wrappers around this primitive.

**State Management** requires `KeepAliveID()` if you call `ButtonBehavior()` without a preceding `ItemAdd`. This ensures the ID remains active for the current frame even when bypassing standard registration, as noted in the `KeepAliveID` implementation in [`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp).

**Navigation Integration** happens automatically when using `ItemAdd()`, but advanced scenarios may require manual key ownership via `SetKeyOwner()` from [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) to handle keyboard shortcuts properly.

**Rendering** uses the window's draw list (`window->DrawList`) to push visual geometry. After handling interaction, draw your widget using functions like `AddRectFilled()` or `RenderFrame()`, applying style colors based on the hover and active states captured earlier.

## The Standard Implementation Pattern

To add custom widgets in Dear ImGui that function as first-class citizens, follow this six-step workflow:

1. **Create a stable ID** using `ImGuiID id = GetID("MyWidget##unique");`

2. **Define the interaction rectangle** with `ImRect bb = ImRect(pos, pos + size);`

3. **Register the item** by calling `ItemSize(bb)` followed by `if (!ItemAdd(bb, id)) return false;`

4. **Process input** via `ButtonBehavior(bb, id, &hovered, &held, flags);`

5. **Update state** based on interaction flags, such as checking `hovered && IsMouseClicked(0)` for clicks

6. **Render** the visual representation using `window->DrawList` primitives, applying style variations for hover and active states

This sequence guarantees your widget participates in ImGui's focus, navigation, clipping, and automatic state tracking systems.

## Common Pitfalls and Solutions

When implementing custom widgets, several specific mistakes can break functionality:

- **Duplicate ID Conflicts**: Overlapping rectangles reusing the same ID cause navigation errors. Fix this by using distinct ID seeds or temporarily enabling `ImGuiItemFlags_AllowDuplicateId`.

- **Missing ItemAdd**: Without calling `ItemAdd()`, your widget bypasses navigation and clipping. If you deliberately skip `ItemAdd()` for a raw input widget, you must call `KeepAliveID(id)` after `ButtonBehavior()`.

- **Incorrect Button Flags**: Using the wrong `ImGuiButtonFlags` (such as forgetting `ImGuiButtonFlags_NoNavFocus`) produces unwanted navigation behavior. Review the flags defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h).

- **Invisible Widgets**: Widgets that process input but never push vertices to the draw list appear invisible. Always render something after interaction handling.

## Complete Code Examples

### Binary Toggle Switch

This example demonstrates a complete custom toggle using the core API:

```cpp
bool MyToggle(const char* label, bool* v, const ImVec2& size = ImVec2(0,0))
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems) return false;

    // 1) ID & rectangle
    ImGuiID id = window->GetID(label);
    ImVec2 label_size = ImGui::CalcTextSize(label);
    ImVec2 toggle_size = ImGui::CalcItemSize(size, ImGui::CalcItemWidth(), label_size.y);
    ImRect bb = ImRect(window->DC.CursorPos, window->DC.CursorPos + toggle_size);
    ImGui::ItemSize(bb);
    if (!ImGui::ItemAdd(bb, id)) return false;

    // 2) Interaction
    bool hovered, held;
    ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
    if (ImGui::IsItemClicked()) *v = !*v;

    // 3) Rendering
    ImU32 col_bg = *v ? ImGui::GetColorU32(ImGuiCol_Button) : ImGui::GetColorU32(ImGuiCol_ButtonHovered);
    ImU32 col_knob = ImGui::GetColorU32(ImGuiCol_Text);
    window->DrawList->AddRectFilled(bb.Min, bb.Max, col_bg, 4.0f);
    ImVec2 knob_pos = *v ? ImVec2(bb.Max.x - toggle_size.y * 0.5f, bb.Min.y + toggle_size.y * 0.5f)
                         : ImVec2(bb.Min.x + toggle_size.y * 0.5f, bb.Min.y + toggle_size.y * 0.5f);
    window->DrawList->AddCircleFilled(knob_pos, toggle_size.y * 0.4f, col_knob);
    ImGui::RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, ImVec2(0.5f,0.5f), NULL);
    return true;
}

```

*Key implementation details*: Uses `GetID` for identity, `ItemAdd` for registration, `ButtonBehavior` for state detection, and draw list primitives for the visual track and knob.

### Custom Horizontal Slider

This draggable slider implements value manipulation via mouse delta:

```cpp
bool MyHorizontalSlider(const char* label, float* value, float v_min, float v_max, const ImVec2& size = ImVec2(0,0))
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems) return false;

    ImGuiID id = window->GetID(label);
    ImVec2 slider_size = ImGui::CalcItemSize(size, ImGui::CalcItemWidth(), ImGui::GetFrameHeight());
    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + slider_size);
    ImGui::ItemSize(bb);
    if (!ImGui::ItemAdd(bb, id)) return false;

    // Interaction – we only need ButtonBehavior; no separate ItemAdd is required.
    bool hovered, held;
    ImGui::ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None);
    if (held)
    {
        float mouse_delta = ImGui::GetIO().MouseDelta.x;
        float proportion = mouse_delta / (bb.Max.x - bb.Min.x);
        *value = ImClamp(*value + proportion * (v_max - v_min), v_min, v_max);
    }

    // Rendering
    float t = ImSaturate((*value - v_min) / (v_max - v_min));
    ImU32 col_bg = ImGui::GetColorU32(ImGuiCol_FrameBg);
    ImU32 col_fg = ImGui::GetColorU32(ImGuiCol_SliderGrabActive);
    window->DrawList->AddRectFilled(bb.Min, bb.Max, col_bg, ImGui::GetStyle().FrameRounding);
    ImVec2 grab_bb_min = ImLerp(bb.Min, bb.Max, ImVec2(t, 0.0f));
    ImVec2 grab_bb_max = ImLerp(bb.Min, bb.Max, ImVec2(t, 1.0f));
    window->DrawList->AddRectFilled(grab_bb_min, grab_bb_max, col_fg, ImGui::GetStyle().FrameRounding);
    ImGui::RenderTextClipped(bb.Min, bb.Max, label, NULL, NULL, ImVec2(0.5f,0.5f), NULL);
    return held;
}

```

*Key implementation details*: Calculates value changes from `MouseDelta` while held, uses `ImLerp` for proportional positioning, and renders the grab bar using the same bounding box processed by `ButtonBehavior`.

### Composite Color Picker Button

This widget combines custom rendering with ImGui's built-in popup functionality:

```cpp
bool MyColorButton(const char* label, ImVec4* col, ImGuiColorEditFlags flags = 0)
{
    ImGuiWindow* window = ImGui::GetCurrentWindow();
    if (window->SkipItems) return false;

    ImGuiID id = window->GetID(label);
    ImVec2 size = ImGui::CalcItemSize(ImVec2(0,0), ImGui::GetFrameHeight(), ImGui::GetFrameHeight());
    ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    ImGui::ItemSize(bb);
    if (!ImGui::ItemAdd(bb, id)) return false;

    // Interaction – open ImGui's native color picker on click
    bool clicked = ImGui::ButtonBehavior(bb, id, NULL, NULL, ImGuiButtonFlags_None);
    if (clicked && ImGui::BeginPopupContextItem(label))
    {
        ImGui::ColorPicker4("##picker", (float*)col, flags);
        ImGui::EndPopup();
    }

    // Rendering – simple filled rectangle with the supplied color
    ImU32 col_u32 = ImGui::ColorConvertFloat4ToU32(*col);
    window->DrawList->AddRectFilled(bb.Min, bb.Max, col_u32, ImGui::GetStyle().FrameRounding);
    // optional border
    window->DrawList->AddRect(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Border));
    return clicked;
}

```

*Key implementation details*: Uses `ButtonBehavior` to detect clicks for opening a context popup, demonstrating how custom widgets can leverage existing Dear ImGui editors while maintaining custom visual presentation.

## Essential Source Files for Reference

Understanding the following files in the ocornut/imgui repository is crucial for advanced widget development:

- **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)**: Contains public API declarations including ID helpers, `PushItemFlag`, and `ImGuiButtonFlags` definitions.

- **[`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)**: Houses core item registration functions (`ItemAdd`, `ItemSize`, `KeepAliveID`) and rendering utilities like `RenderFrame`.

- **[`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp)**: Provides the `ButtonBehavior` implementation and serves as the definitive reference for how built-in widgets combine interaction and rendering.

- **[`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h)**: Exposes advanced utilities such as `SetKeyOwner` for keyboard handling and internal flags for specialized behaviors.

- **[`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp)**: Contains extensive example code demonstrating various custom widget patterns and advanced usage scenarios.

- **`backends/`**: Directory containing backend implementations that illustrate how draw lists are flushed to the GPU, useful when implementing custom rendering beyond standard draw list primitives.

## Summary

To successfully add custom widgets in Dear ImGui:

- **Always generate a unique ID** using `GetID()` to prevent state collisions

- **Register with ItemAdd()** to enable navigation, clipping, and focus management

- **Use ButtonBehavior()** as the universal interaction primitive for hover and click detection

- **Call KeepAliveID()** when bypassing ItemAdd for raw input-only widgets

- **Render via window->DrawList** using the same bounding box processed by interaction code

- **Reference imgui_widgets.cpp** for authoritative patterns on combining behavior and presentation

## Frequently Asked Questions

### What is the minimum code required for a functional custom widget?

The absolute minimum requires three elements: generate an ID with `GetID()`, define an `ImRect` bounding box, and call `ButtonBehavior()` with that rectangle and ID. However, to function properly within layouts, you should also call `ItemSize()` before `ItemAdd()`, and to remain visible, you must render geometry to the draw list using the same bounding box coordinates.

### Why does my custom widget not respond to clicks?

The most common cause is missing `ItemAdd()` registration, which prevents the widget from being processed in the interaction loop. Alternatively, you may have duplicate ID conflicts where another widget is consuming the input, or you might be checking for clicks incorrectly—use the `hovered` and `held` outputs from `ButtonBehavior()` rather than raw mouse position checks to respect ImGui's input stack.

### How do I handle keyboard navigation for custom widgets?

When you use `ItemAdd()` with the default parameters, your widget automatically participates in tab navigation and focus systems. For advanced keyboard handling, such as arrow key navigation within a custom grid, manually call `SetKeyOwner()` from [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) to claim ownership of specific `ImGuiKey` values, preventing other widgets from consuming those inputs while your widget is focused.

### Can I create custom widgets without including imgui_internal.h?

Yes, for basic widgets you only need [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) and standard API functions like `ButtonBehavior()`, `ItemAdd()`, and `GetID()`. However, [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h) is required for advanced features such as manual key ownership (`SetKeyOwner`), duplicate ID flags, and accessing the internal `ImGuiWindow` structure directly for specialized draw list operations. Most production custom widgets eventually require at least partial inclusion of the internal API.