# How to Create Custom Widgets in Dear ImGui Using the Drawing API

> Learn to create custom widgets in Dear ImGui using the drawing API. Discover how to manage input and draw directly for unique UI elements.

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

---

**You create custom widgets in Dear ImGui by allocating an interaction rectangle with `InvisibleButton()`, querying input state with `IsItemHovered()` and `ButtonBehavior()`, then issuing draw commands to an `ImDrawList` retrieved via `GetWindowDrawList()`.**

Dear ImGui's immediate-mode architecture allows you to extend the library beyond its built-in controls by drawing directly into the vertex buffers. This guide shows you how to build interactive UI components in the **ocornut/imgui** repository by combining the internal input handling API with low-level drawing primitives defined in [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp) and declared in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h).

## Understanding the ImDrawList Architecture

Every window in Dear ImGui maintains its own **ImDrawList** buffer accessed via `ImGui::GetWindowDrawList()`. There are also global **background** and **foreground** draw lists available through `ImGui::GetBackgroundDrawList()` and `ImGui::GetForegroundDrawList()` for rendering outside window boundaries.

The `ImDrawList` structure (defined in [[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) around lines 3309–3335](https://github.com/ocornut/imgui/blob/master/imgui.h#L3309)) accumulates vertex and index data that the backend uploads to the GPU each frame. Because Dear ImGui operates in immediate mode, your custom widget must redraw its geometry every frame—there is no persistent widget object or retained geometry.

Key implementation files to reference:

- **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** – Contains implementations of all `AddLine()`, `AddRect()`, `AddCircleFilled()`, and other primitives.
- **[`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h)** – Exposes `ItemAdd()`, `ButtonBehavior()`, and other internal layout helpers.
- **[`imgui.cpp`](https://github.com/ocornut/imgui/blob/main/imgui.cpp)** – Houses core window handling and the draw list management system.

## The Six-Step Pattern for Custom Widgets

All custom widgets follow a consistent pattern that separates interaction logic from rendering.

### 1. Reserve Screen Space

Allocate a rectangle and register it with the layout system using **`ImGui::InvisibleButton()`** or the low-level `ItemAdd()` API. This creates a hit-testable region and generates a unique identifier.

```cpp
const ImVec2 size = ImVec2(100, 30);
ImGui::InvisibleButton("my_widget", size);
// Or manually:
// ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
// ImGui::ItemSize(bb);
// if (!ImGui::ItemAdd(bb, id)) return false;

```

### 2. Capture Input State

Query interaction flags immediately after reserving space. Use **`IsItemHovered()`** for mouse overlap and **`ButtonBehavior()`** for click detection.

```cpp
bool hovered = ImGui::IsItemHovered();
bool active = ImGui::IsItemActive();
bool clicked = ImGui::IsMouseClicked(0);

```

### 3. Retrieve the Draw List

Get the current window's draw list to issue rendering commands. For overlay elements spanning multiple windows, use `GetForegroundDrawList()`.

```cpp
ImDrawList* draw_list = ImGui::GetWindowDrawList();
// ImDrawList* fg_list = ImGui::GetForegroundDrawList();

```

### 4. Compute Geometry

Convert from ImGui coordinates to pixel positions using **`GetItemRectMin()`** and **`GetItemRectMax()`**.

```cpp
ImVec2 p_min = ImGui::GetItemRectMin();
ImVec2 p_max = ImGui::GetItemRectMax();
ImVec2 center = (p_min + p_max) * 0.5f;

```

### 5. Issue Draw Commands

Call `ImDrawList` methods to render shapes. Use **`AddRectFilled()`** for backgrounds, **`AddCircle()`** for rounded elements, and **`AddText()`** for labels.

```cpp
draw_list->AddRectFilled(p_min, p_max, IM_COL32(100, 100, 100, 255));
draw_list->AddRect(p_min, p_max, IM_COL32(255, 255, 255, 255));

```

### 6. Return Logical State

Return a boolean or value representing the widget's state to the caller.

```cpp
return clicked; // or return *v for toggles

```

## Practical Implementation: Custom Toggle Button

This complete example implements a circular toggle button using the internal API for proper layout integration and interaction handling.

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

    ImGuiContext& g = *GImGui;
    const ImGuiID id = window->GetID(label);
    const ImVec2 label_size = ImGui::CalcTextSize(label);
    const ImVec2 final_size = ImGui::CalcItemSize(size, 
        label_size.x + g.Style.FramePadding.x * 2,
        label_size.y + g.Style.FramePadding.y * 2);
    
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + final_size);
    ImGui::ItemSize(bb, g.Style.FramePadding.y);
    if (!ImGui::ItemAdd(bb, id)) return false;

    bool hovered = ImGui::ItemHoverable(bb, id);
    bool pressed = ImGui::ButtonBehavior(bb, id, nullptr, nullptr, ImGuiButtonFlags_None);
    if (pressed) *v = !*v;

    ImDrawList* draw = ImGui::GetWindowDrawList();
    ImVec2 center = bb.GetCenter();
    float radius = ImMin(bb.GetWidth(), bb.GetHeight()) * 0.5f;

    ImU32 bg_col = hovered ? ImGui::GetColorU32(ImGuiCol_ButtonHovered)
                           : ImGui::GetColorU32(ImGuiCol_Button);
    draw->AddCircleFilled(center, radius, bg_col, 32);

    if (*v)
    {
        ImU32 check_col = ImGui::GetColorU32(ImGuiCol_Text);
        draw->AddLine(center + ImVec2(-radius * 0.4f, 0), 
                     center + ImVec2(0, radius * 0.4f), check_col, 2.0f);
        draw->AddLine(center + ImVec2(0, radius * 0.4f), 
                     center + ImVec2(radius * 0.4f, -radius * 0.4f), check_col, 2.0f);
    }

    if (label_size.x > 0.0f)
        ImGui::RenderText(bb.Max + ImVec2(g.Style.ItemSpacing.x, 0.0f), label);

    return pressed;
}

```

The function uses **`ItemAdd()`** for proper clipping and navigation support, **`ItemHoverable()`** for hover detection, and **`ButtonBehavior()`** to handle mouse clicks according to ImGui's standard button rules.

## Practical Implementation: Custom Graph Widget

This example renders a line graph using `AddLine()` to plot values within a defined canvas area.

```cpp
void PlotCustomGraph(const char* label, const float* values, int values_count,
                     const ImVec2& size = ImVec2(0, 0))
{
    ImGui::BeginGroup();
    ImGui::PushID(label);
    ImGui::InvisibleButton("canvas", size);
    
    ImVec2 p0 = ImGui::GetItemRectMin();
    ImVec2 p1 = ImGui::GetItemRectMax();
    ImDrawList* draw = ImGui::GetWindowDrawList();

    draw->AddRectFilled(p0, p1, ImGui::GetColorU32(ImGuiCol_FrameBg));
    draw->AddLine(ImVec2(p0.x, p1.y), ImVec2(p1.x, p1.y), 
                 ImGui::GetColorU32(ImGuiCol_Text));
    draw->AddLine(ImVec2(p0.x, p0.y), ImVec2(p0.x, p1.y), 
                 ImGui::GetColorU32(ImGuiCol_Text));

    if (values_count > 1)
    {
        float inv_count = 1.0f / (float)(values_count - 1);
        ImVec2 prev = ImVec2(p0.x, p1.y - values[0] * (p1.y - p0.y));
        
        for (int i = 1; i < values_count; ++i)
        {
            ImVec2 cur = ImVec2(
                p0.x + (p1.x - p0.x) * i * inv_count,
                p1.y - values[i] * (p1.y - p0.y)
            );
            draw->AddLine(prev, cur, ImGui::GetColorU32(ImGuiCol_PlotLines));
            prev = cur;
        }
    }

    ImGui::PopID();
    ImGui::EndGroup();
}

```

## Handling Clipping and Coordinate Spaces

Restrict drawing to specific areas using **`ImGui::PushClipRect()`** and **`ImGui::PopClipRect()`**. This is essential when implementing scrollable custom widgets or nested canvases.

```cpp
ImGui::PushClipRect(p_min, p_max, true); // intersect_with_current_clip=true
draw_list->AddCircle(center, radius, col);
ImGui::PopClipRect();

```

When drawing outside the current window bounds—such as for tooltips, drag previews, or global overlays—use the foreground draw list obtained from `ImGui::GetForegroundDrawList()`. This list renders on top of all windows and bypasses the current window's clipping rectangle.

## Summary

- **Reserve space** with `InvisibleButton()` or `ItemAdd()` to create an interaction rectangle and obtain an ID.
- **Query input** using `IsItemHovered()`, `IsItemActive()`, or `ButtonBehavior()` immediately after reserving space.
- **Retrieve the draw list** via `GetWindowDrawList()` for window-local content, or `GetForegroundDrawList()` for global overlays.
- **Draw every frame** because Dear ImGui is immediate-mode; geometry persists only until the next frame.
- **Reference [`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)** for primitive implementations and [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) for `ImDrawList` method signatures.

## Frequently Asked Questions

### What is the difference between `GetWindowDrawList` and `GetBackgroundDrawList`?

`GetWindowDrawList()` returns the draw list for the current window, automatically clipped to that window's boundaries and transformed by its scroll position. `GetBackgroundDrawList()` and `GetForegroundDrawList()` return global draw lists that render behind or in front of all windows, using screen-space coordinates regardless of the current window context. Use global lists for elements that must persist across window boundaries or render outside the current window's clipped region.

### How do I handle mouse interaction in a custom widget?

First, reserve space with `InvisibleButton()` to generate a hit rectangle and ID. Then check `IsItemHovered()` to detect mouse overlap and `IsItemClicked()` or `ButtonBehavior()` to handle clicking. For complex widgets requiring drag operations, use `ButtonBehavior()` with flags like `ImGuiButtonFlags_MouseButtonLeft` and query `IsItemActive()` during subsequent frames to track ongoing interaction.

### Where are the `ImDrawList` primitives implemented?

All drawing methods—`AddLine()`, `AddRect()`, `AddCircleFilled()`, `AddText()`, etc.—are implemented in **[`imgui_draw.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_draw.cpp)**. The structure definition and public API declarations reside in **[`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h)** around lines 3309–3335, while advanced internal helpers are found in [`imgui_internal.h`](https://github.com/ocornut/imgui/blob/main/imgui_internal.h).

### Do custom widgets need to cache their geometry between frames?

No. Dear ImGui is an immediate-mode library, meaning you redraw your widget's geometry every frame by issuing new commands to the `ImDrawList`. The library rebuilds the vertex buffers from scratch each frame, so there is no benefit to caching previous draw commands. Focus on optimizing the CPU-side geometry calculation rather than attempting to retain GPU buffers.