# How to Extend Dear ImGui with Custom Widgets: A Complete Guide to the Internal API

> Extend Dear ImGui with custom widgets easily. Learn to use the internal API for unique IDs, bounding boxes, interactions, and rendering without core library changes.

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

---

**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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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`:

```cpp
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`](https://github.com/ocornut/imgui/blob/main/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:

```cpp
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`](https://github.com/ocornut/imgui/blob/main/imgui_demo.cpp) inside the `ShowDemoWindow()` function:

```cpp
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`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)** – Contains `ItemAdd` (line 11455), `KeepAliveID` (line 5668), and the navigation handling logic.
- **[`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp)** – Implements built-in widgets and exposes `ButtonBehavior` (line 545) for reuse in custom controls.
- **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** – Defines `ImDrawList` and all immediate-mode rendering primitives.
- **[`imgui_demo.cpp`](https://github.com/ocornut/imgui/blob/main/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 by `ItemAdd()` to participate in layout and clipping.
- **Handle interaction** through `ButtonBehavior()` to reuse ImGui’s robust input state machine.
- **Render** using `ImDrawList` primitives for automatic style and clipping integration.
- **Place code** in your own source files—modifying `ocornut/imgui` is unnecessary for custom widgets.
- **Reference** [`imgui_widgets.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_widgets.cpp) line 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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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.