How to Implement Drag and Drop Functionality in Dear ImGui: A Complete API Guide

Dear ImGui provides an immediate-mode drag-and-drop system where BeginDragDropSource() initiates drags from any widget, SetDragDropPayload() packages the data, and BeginDragDropTarget() with AcceptDragDropPayload() handles the drop on compatible targets.

Dear ImGui's drag-and-drop implementation operates entirely within the immediate-mode paradigm, requiring no persistent state management or complex event registration. According to the ocornut/imgui source code, the system relies on a simple three-phase workflow executed frame-by-frame: detecting drag initiation on source widgets, transporting payload data via the ImGuiPayload structure, and accepting drops on target widgets. This architecture allows developers to implement complex interactions like item reordering, file imports, or property transfers with minimal boilerplate.

Understanding the Drag and Drop Workflow

The API divides drag-and-drop operations into distinct source and target phases, each bracketed by begin/end functions declared in imgui.h.

Source Phase: Initiating the Drag

To make a widget draggable, call BeginDragDropSource() immediately after submitting the widget. This function returns true once the user holds the mouse button and begins dragging.

  • BeginDragDropSource([flags]) – Defined in imgui.h at lines 73‑79. Returns true when the widget becomes active drag source.
  • SetDragDropPayload(type, data, size, cond) – Defined in imgui.h at lines 80‑81. Copies the payload data (max 32 characters for the type string) into internal storage, allowing the original buffer to be freed immediately after the call.
  • EndDragDropSource() – Defined in imgui.h at line 82. Must be called only when BeginDragDropSource() returned true.

Target Phase: Receiving the Drop

To accept drops, submit your target widget (using any standard item call like Button() or InvisibleButton()), then call BeginDragDropTarget().

  • BeginDragDropTarget() – Defined in imgui.h at lines 84‑85. Returns true while a compatible payload hovers over the item's bounding box.
  • AcceptDragDropPayload(type, [flags]) – Defined in imgui.h at lines 86‑87. Returns a pointer to ImGuiPayload (defined in imgui_internal.h) if the hovering payload matches the specified type string; otherwise returns NULL.
  • EndDragDropTarget() – Defined in imgui.h at line 88. Must be called only when BeginDragDropTarget() returned true.

The Payload Structure

The ImGuiPayload structure (defined in imgui_internal.h) contains the Data pointer, DataSize, and Type string. According to the implementation in imgui.cpp around line 15292, the system copies payload data when SetDragDropPayload is called, ensuring the payload remains valid even if the source widget is destroyed during the drag operation.

Practical Implementation Examples

Swapping Items Between Buttons

This pattern from imgui_demo.cpp demonstrates a complete copy/move/swap interaction using button widgets as both sources and targets. Notice the use of PushID()/PopID() to ensure unique ImGuiIDs for drag detection.

static const char* names[3] = { "Alice", "Bob", "Carol" };
for (int n = 0; n < IM_ARRAYSIZE(names); n++)
{
    ImGui::PushID(n);
    ImGui::Button(names[n], ImVec2(80, 30));

    // ---- Drag source -------------------------------------------------
    if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
    {
        // Payload carries the index of the item.
        ImGui::SetDragDropPayload("MY_DND_PAYLOAD", &n, sizeof(int));
        ImGui::Text("Dragging %s", names[n]);   // optional preview tooltip
        ImGui::EndDragDropSource();
    }

    // ---- Drop target -------------------------------------------------
    if (ImGui::BeginDragDropTarget())
    {
        if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND_PAYLOAD"))
        {
            int src_idx = *(const int*)payload->Data;
            // Example: swap the two entries.
            ImGui::Swap(names[n], names[src_idx]);
        }
        ImGui::EndDragDropTarget();
    }
    ImGui::PopID();
}

Dragging Colors Between Widgets

This minimal example shows dragging an ImVec4 color from a color button to a custom rectangle target using InvisibleButton() as the drop zone.

static ImVec4 source_color = ImVec4(0.4f, 0.7f, 0.2f, 1.0f);
static ImVec4 target_color = ImVec4(0.2f, 0.3f, 0.8f, 1.0f);

ImGui::ColorButton("Source", source_color, ImGuiColorEditFlags_NoLabel);
if (ImGui::BeginDragDropSource())
{
    ImGui::SetDragDropPayload("COL_DND", &source_color, sizeof(ImVec4));
    ImGui::Text("Drag Color");
    ImGui::EndDragDropSource();
}

ImGui::SameLine();
ImGui::InvisibleButton("Target", ImVec2(80, 80));
if (ImGui::BeginDragDropTarget())
{
    if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("COL_DND"))
        target_color = *(const ImVec4*)payload->Data;
    ImGui::EndDragDropTarget();
}
ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(),
                                          ImGui::GetItemRectMax(),
                                          ImGui::ColorConvertFloat4ToU32(target_color));

Key Implementation Details

Widget IDs are mandatory – Drag-and-drop operations depend on ImGuiID. If you attempt to use BeginDragDropSource() after a widget without a natural ID (such as ImGui::Text), you must wrap the content with PushID()/PopID() pairs to generate a unique identifier.

Available flags – The ImGuiDragDropFlags enumeration (defined in imgui.h) provides several control options:

  • ImGuiDragDropFlags_SourceNoPreviewTooltip – Disables the default preview tooltip on the source side.
  • ImGuiDragDropFlags_AcceptNoPreviewTooltip – Hides the tooltip when hovering over a target.
  • ImGuiDragDropFlags_AcceptBeforeDelivery – Allows the target to accept and preview the payload before the mouse button is released (peek mode).

Nesting restrictions – The implementation in imgui.cpp (around line 15037) asserts that sources cannot be nested inside other sources or targets. Calling BeginDragDropSource() while another drag operation is active triggers IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()").

Payload lifetime – Because SetDragDropPayload() copies the data immediately, the source buffer can be stack-allocated or reused once the function returns. The internal copy persists until the drag operation completes or the payload is accepted by a target.

Summary

  • Dear ImGui's drag-and-drop system is immediate-mode, utilizing BeginDragDropSource() and BeginDragDropTarget() bracketed calls defined in imgui.h.
  • Payload data is copied and transported via SetDragDropPayload(), with type strings limited to 32 characters.
  • Widget IDs are essential for source detection; use PushID()/PopID() for text or custom draw widgets.
  • The ImGuiPayload structure provides the data pointer and size, accessible via AcceptDragDropPayload() as implemented in imgui.cpp around line 15292.
  • Source and target blocks cannot be nested, and the API provides flags to control tooltip visibility and early acceptance behavior.

Frequently Asked Questions

What is the maximum size for drag-and-drop payload type strings?

Dear ImGui limits payload type strings to 32 characters (including the null terminator). This is enforced internally in imgui.cpp during the call to SetDragDropPayload(). Exceeding this limit results in the type being truncated, potentially causing type mismatches between source and target.

Can I implement drag and drop on text labels or custom drawn elements?

Yes, but you must explicitly provide an ImGuiID using PushID() before the text or drawing command, and PopID() after. The drag-and-drop detection mechanism in imgui.cpp relies on the last item ID returned by GetID(), so widgets without automatic ID generation (like ImGui::Text or raw draw lists) require manual ID management to serve as valid sources or targets.

How do I preview the payload data before the user releases the mouse button?

Use the ImGuiDragDropFlags_AcceptBeforeDelivery flag when calling AcceptDragDropPayload(). This allows the target to access payload->Data during the hover state (while the mouse button is still held), enabling real-time visual feedback such as highlighting insertion points or displaying preview overlays before the drop is finalized.

Why does my drag operation trigger an assertion error?

The most common cause is nesting drag-and-drop source or target blocks, which triggers the IM_ASSERT(g.DragDropWithinSource) check in imgui.cpp. Ensure you never call BeginDragDropSource() inside another source or target block, and always verify the return value of begin functions before calling the corresponding EndDragDropSource() or EndDragDropTarget().

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 →