How to Create Custom ImGui Widgets Using `imgui_internal.h`

Dear ImGui's internal header (imgui_internal.h) exposes low-level structures, flags, and helper functions that enable you to build bespoke widgets by leveraging state storage, custom rectangles, and internal API entry points while maintaining compatibility with the immediate-mode rendering pipeline.

Dear ImGui (ocornut/imgui) provides a robust immediate-mode GUI framework for C++, but sophisticated applications often require controls beyond the standard library offerings. By tapping into the internal machinery defined in imgui_internal.h, you can extend the library with custom combos, specialized drag-drop targets, and novel interaction patterns while maintaining the library's core architectural principles.

Core Components in imgui_internal.h

The internal header organizes functionality into logical sections that directly support widget extension. Understanding these compartments is essential for writing safe, compatible custom controls.

Widget Flags and Enums

The internal API defines extended flag bits that augment public enumerations. For example, ImGuiComboFlags_CustomPreview allows you to override the default preview rendering of combo boxes. These flag definitions reside in the Widgets support section around line 1090, providing the bitwise markers necessary to signal specialized behavior to ImGui's internal state machine.

State Storage and Context

Custom widgets require temporary state persistence across frames. The internal header exposes members like BeginComboDepth and functions such as BeginComboPreview() and EndComboPreview() within the Widgets support section (lines 2535-3533). These mechanisms allow you to store per-widget data—such as preview rectangle dimensions—without polluting the public API.

Custom Rectangle Hit-Testing

When building widgets that don't conform to standard item boundaries, you need precise control over hit-testing regions. The BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) function, declared in the Inputs support section at line 3661, registers arbitrary bounding boxes as valid interaction targets. This is crucial for "in-place" widgets or drop zones that overlay existing content.

Geometric Helper Structures

The header provides foundational math utilities at lines 889-925, including ImRect for axis-aligned bounding boxes, ImBitVector for efficient boolean storage, and ImSpan<> for memory-safe array views. These structures form the geometric backbone of custom widget implementations.

Internal API Entry Points

The ImGui internal API section (lines 389-400) declares functions prefixed with ImGui:: that bypass public safety checks. These entry points allow direct manipulation of window draw lists, ID stacks, and item state data, but require strict adherence to ImGui's internal patterns to avoid clipping errors or state corruption.

The Custom Widget Workflow

Building a custom widget follows a predictable pattern that mirrors ImGui's internal architecture:

  1. Define a unique ID using ImGui::GetID() or hash-based generation with ImHashStr to ensure the widget integrates with ImGui's navigation and focus systems.
  2. Create a hit-box by constructing an ImRect that encompasses your widget's interactive area.
  3. Register the hit-box with BeginDragDropTargetCustom(bb, id) or similar custom registration functions to enable input processing.
  4. Render geometry using ImDrawList methods like AddRectFilled() and AddText() to paint your widget's appearance.
  5. Query interaction state via ImGui::IsItemHovered(), IsItemActive(), or custom flag checks to determine user input.
  6. Cleanup state by calling corresponding end-functions (e.g., EndComboPreview()) to restore internal context.

Because these calls bypass higher-level safety checks, you must manually replicate ImGui's internal state-machine patterns: pushing IDs onto the stack, adding items to the window's draw list, and updating LastItemData to maintain consistent clipping and navigation behavior.

Practical Implementation Examples

The following examples demonstrate concrete usage of the internal API for common customization scenarios.

Custom Combo Preview

This implementation uses ImGuiComboFlags_CustomPreview to render a bespoke preview area within a standard combo box:

bool MyCombo(const char* label, const char* preview_text, const std::vector<const char*>& items)
{
    // 1️⃣ Create a unique ID for the combo
    ImGuiID combo_id = ImGui::GetID(label);

    // 2️⃣ Begin the combo with the custom flag
    if (!ImGui::BeginCombo(label, preview_text,
                           ImGuiComboFlags_CustomPreview))
        return false;        // Combo not opened → nothing to do

    // 3️⃣ Custom preview area – we render our own preview inside the combo button
    ImGui::BeginComboPreview();                     // ← internal call (lines 1187-1189)
    ImGui::GetWindowDrawList()->AddRectFilled(
        ImGui::GetItemRectMin(), ImGui::GetItemRectMax(),
        IM_COL32(30, 30, 80, 255));
    ImGui::GetWindowDrawList()->AddText(
        ImGui::GetItemRectMin() + ImVec2(4, 4),
        IM_COL32(255, 255, 255, 255), preview_text);
    ImGui::EndComboPreview();                       // ← internal cleanup

    // 4️⃣ List the selectable items
    for (int i = 0; i < (int)items.size(); ++i)
    {
        const bool selected = (preview_text == items[i]);
        if (ImGui::Selectable(items[i], selected))
        {
            // User selected a new entry → update preview (outside this function)
            // Return true to signal the caller to replace the preview string.
            ImGui::EndCombo();
            return true;
        }
    }
    ImGui::EndCombo();
    return false;
}

The critical internal calls BeginComboPreview() and EndComboPreview() are declared in imgui_internal.h at lines 1187-1189, allowing you to inject custom rendering between the combo header and its dropdown list.

Custom Drag-Drop Target

This example creates a non-standard drop zone using BeginDragDropTargetCustom():

bool MyCustomDropTarget(const ImRect& rect, ImGuiID custom_id)
{
    // 1️⃣ Tell ImGui that this rectangle can be a drag‑drop target
    if (!ImGui::BeginDragDropTargetCustom(rect, custom_id))  // ← line 3661
        return false;        // No payload hovering this rect

    // 2️⃣ Accept a payload of a user‑defined type
    const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_CUSTOM_TYPE");
    if (payload)
    {
        // Payload data is available in payload->Data (raw bytes)
        // Here we just print the size for demonstration
        printf("Dropped payload size: %zu bytes\n", payload->DataSize);
        ImGui::EndDragDropTarget();               // ← internal cleanup
        return true;
    }
    ImGui::EndDragDropTarget();
    return false;
}

This pattern—declare the custom area → register it with the internal API → draw → query interaction → clean up—applies to all widgets built against imgui_internal.h.

Essential Files for Custom Widget Development

When extending ImGui, these source files provide the necessary definitions and reference implementations:

  • imgui_internal.h — Central definition of internal flags, helper structs (ImRect, ImBitVector), and the low‑level functions (BeginComboPreview, BeginDragDropTargetCustom) required for custom widget construction.
  • imgui.h — The public façade that forwards to the internal API; contains the public enums (ImGuiComboFlags) that you extend with internal flag bits.
  • imconfig.h (optional) — User configuration file where you can define IMGUI_INCLUDE_IMGUI_INTERNAL_H or customize compile-time behavior.
  • imgui_demo.cpp — Reference implementations showing concrete widget patterns; useful as a template when adapting internal calls.

Summary

  • imgui_internal.h exposes low-level API functions, geometric helpers (ImRect, ImSpan), and extended flags (ImGuiComboFlags_CustomPreview) necessary for custom widget creation.
  • Custom widgets require explicit state management: you must manually handle ID generation, hit-box registration via BeginDragDropTargetCustom(), and cleanup via corresponding end-functions.
  • The internal API bypasses safety checks, requiring strict adherence to ImGui's state-machine patterns (pushing IDs, updating LastItemData, managing draw lists) to prevent rendering artifacts.
  • Key functions like BeginComboPreview() (lines 1187-1189) and BeginDragDropTargetCustom() (line 3661) enable specialized rendering and input handling without modifying the core library.
  • Workflow consistency follows the pattern: define ID → create ImRect → register with internal API → render → query state → cleanup.

Frequently Asked Questions

What is the difference between imgui.h and imgui_internal.h?

imgui.h provides the stable public API with guaranteed backward compatibility, while imgui_internal.h exposes implementation details that may change between versions. The internal header contains the low-level structures and functions—such as ImRect helpers and BeginDragDropTargetCustom()—that widget developers need to access ImGui's internal state machinery.

Is it safe to use the internal API in production code?

Using imgui_internal.h requires accepting that function signatures and behavior may change in future releases. However, the internal API is remarkably stable in practice, and many production applications rely on it for custom widgets. To minimize breakage, pin your project to specific ImGui versions or wrap internal calls in abstraction layers that you can update when upgrading.

How do I handle ID conflicts when creating custom widgets?

Always generate unique IDs using ImGui::GetID(label) or ImHashStr() combined with your widget's label and optional index parameters. When building composite widgets (containers with multiple interactive elements), explicitly push and pop ID scopes using ImGui::PushID() and ImGui::PopID() to ensure child elements don't collide with sibling widgets or other windows.

Can I use the internal API with Dear ImGui's docking and multi-viewport features?

Yes, the internal API fully supports advanced features like docking and multi-viewport rendering. When using BeginDragDropTargetCustom() or similar functions, ensure your ImRect coordinates are in the correct window space, and always pair internal begin/end calls within the same window context to maintain proper clip rect and viewport synchronization.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →