Handling Keyboard and Mouse Input Events Through Dear ImGuiIO: A Complete Guide

Dear ImGui processes all keyboard, mouse, and text input through the ImGuiIO structure, which acts as a centralized I/O hub that backends populate via Add* functions and the UI queries each frame to drive interaction.

The ImGuiIO structure is the single data-exchange point between your application and Dear ImGui. Whether you are using a standard backend like GLFW or building a custom integration for a proprietary engine, understanding how to feed input events through ImGuiIO is essential for responsive UI navigation.

The ImGuiIO Structure: Your Input Pipeline

The ImGuiIO structure is declared in imgui.h (around line 2430) and serves as the authoritative source for all input state. Rather than modifying internal arrays directly, your backend calls specific Add* methods to queue events, making the system frame-rate independent and robust against low-framerate scenarios.

Key member groups inside ImGuiIO include:

  • Configuration flags (ConfigFlags, BackendFlags) – Declare which optional features are active, such as ImGuiConfigFlags_NavEnableKeyboard.
  • Input queues – The Add* functions that backends call: AddKeyEvent, AddKeyAnalogEvent, AddMousePosEvent, AddMouseButtonEvent, AddMouseWheelEvent, and AddInputCharacter.
  • Output flags (WantCaptureMouse, WantCaptureKeyboard, WantTextInput) – Signals set by ImGui during NewFrame() that tell your application whether to consume or forward subsequent input events.
  • State caches (MousePos, MouseDown[], KeysData[]) – Read-only snapshots filled by the Add* calls and consumed by widgets.

How Official Backends Translate Native Events

Standard backends like the GLFW implementation in imgui_impl_glfw.cpp demonstrate the canonical pattern for event translation. Each native callback maps platform-specific codes to the ImGuiKey enumeration, then pushes the data into ImGuiIO.

Keyboard Input Mapping

The ImGui_ImplGlfw_KeyCallback function translates GLFW keycodes using ImGui_ImplGlfw_KeyToImGuiKey, then submits the event:

io.AddKeyEvent(imgui_key, (action == GLFW_PRESS));
io.SetKeyEventNativeData(imgui_key, keycode, scancode);   // legacy support

This pattern appears at lines 494-498 in imgui_impl_glfw.cpp. Modifier keys are handled separately via ImGui_ImplGlfw_UpdateKeyModifiers (lines 10-14), which calls io.AddKeyEvent for each ImGuiMod_* flag to ensure Ctrl, Shift, Alt, and Super states are current.

Mouse Movement and Buttons

For cursor position, the backend forwards coordinates directly:

io.AddMousePosEvent((float)x, (float)y);

Mouse button states are submitted similarly (lines 29-31):

io.AddMouseButtonEvent(button, action == GLFW_PRESS);

Scroll and Text Input

Scroll events are processed in ImGui_ImplGlfw_ScrollCallback (lines 44-46):

io.AddMouseWheelEvent((float)xoffset, (float)yoffset);

Unicode text input is captured via ImGui_ImplGlfw_CharCallback (lines 50-51):

io.AddInputCharacter(c);

Event Queuing and Trickling

Starting with v1.87, Dear ImGui supports the ConfigInputTrickleEventQueue configuration. This feature automatically distributes events that occur between frames—such as a rapid mouse-down and mouse-up sequence—across multiple NewFrame() calls.

Your backend does not implement this logic; it simply calls the Add* functions. ImGui timestamps each entry internally and releases them sequentially to prevent input loss during low-framerate situations. This makes the library resilient when the application runs at 30 FPS but the input device polls at 1000 Hz.

Consuming Input in Your Application

After calling ImGui::NewFrame(), inspect the output flags to determine whether ImGui wants exclusive access to input events:

if (io.WantCaptureMouse) {
    // ImGui is hovering a window or widget; do not send this event to your game
}

if (io.WantCaptureKeyboard) {
    // An InputText is active; consume keyboard events here
}

This design allows seamless integration where ImGui overlays do not interfere with underlying game logic unless intentionally focused.

Implementing a Custom Backend

For platforms without official support, implement the same pattern used in imgui_impl_glfw.cpp. You only need to map native codes to ImGuiKey and call the appropriate Add* methods:

void MyApp_OnKey(int nativeKey, bool down)
{
    ImGuiIO& io = ImGui::GetIO();
    ImGuiKey imgui_key = MyMapNativeToImGuiKey(nativeKey);
    io.AddKeyEvent(imgui_key, down);
}

A minimal manual feeding example (no backend) looks like this:

void FeedImGuiInput()
{
    ImGuiIO& io = ImGui::GetIO();

    // Keyboard: press/release 'A'
    io.AddKeyEvent(ImGuiKey_A, true);
    io.AddKeyEvent(ImGuiKey_A, false);

    // Mouse: move to (400,300)
    io.AddMousePosEvent(400.0f, 300.0f);

    // Left button click
    io.AddMouseButtonEvent(0, true);   // 0 = ImGuiMouseButton_Left
    io.AddMouseButtonEvent(0, false);

    // Wheel scroll up
    io.AddMouseWheelEvent(0.0f, 1.0f);
}

For raw Win32 integration, your window procedure would look similar to this:

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    ImGuiIO& io = ImGui::GetIO();

    switch (msg)
    {
    case WM_KEYDOWN:
    case WM_SYSKEYDOWN:
        io.AddKeyEvent(ImGuiKey_FromVirtualKey((int)wParam), true);
        break;
    case WM_KEYUP:
    case WM_SYSKEYUP:
        io.AddKeyEvent(ImGuiKey_FromVirtualKey((int)wParam), false);
        break;
    case WM_MOUSEMOVE:
        io.AddMousePosEvent((float)GET_X_LPARAM(lParam), (float)GET_Y_LPARAM(lParam));
        break;
    case WM_LBUTTONDOWN:
        io.AddMouseButtonEvent(ImGuiMouseButton_Left, true);
        break;
    case WM_LBUTTONUP:
        io.AddMouseButtonEvent(ImGuiMouseButton_Left, false);
        break;
    case WM_MOUSEWHEEL:
        io.AddMouseWheelEvent(0.0f, (float)GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA);
        break;
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

Summary

  • ImGuiIO in imgui.h is the central structure for all input handling in Dear ImGui.
  • Backends call AddKeyEvent, AddMousePosEvent, AddMouseButtonEvent, and AddMouseWheelEvent rather than modifying internal state directly.
  • The trickle queue (ConfigInputTrickleEventQueue) automatically handles high-frequency input on low-framerate applications without backend changes.
  • Check io.WantCaptureMouse and io.WantCaptureKeyboard after ImGui::NewFrame() to determine whether to consume or forward events.
  • Custom backends follow the same pattern as imgui_impl_glfw.cpp: map native codes to ImGuiKey, then submit via Add* functions.

Frequently Asked Questions

How do I handle modifier keys like Ctrl and Shift in Dear ImGui?

Modifier keys are treated as regular keys in the ImGuiKey enumeration (e.g., ImGuiMod_Ctrl, ImGuiMod_Shift). Your backend should call io.AddKeyEvent(ImGuiMod_Ctrl, true) when the control key is pressed, typically inside your key callback or a dedicated update function like ImGui_ImplGlfw_UpdateKeyModifiers shown in the GLFW backend.

What is the difference between AddKeyEvent and AddKeyAnalogEvent?

AddKeyEvent submits a boolean pressed/released state for digital keys, while AddKeyAnalogEvent submits a float value between 0.0 and 1.0 for analog inputs like gamepad triggers or analog sticks. Most keyboard and mouse integrations use AddKeyEvent exclusively.

Why should I use Add* functions instead of writing directly to io.KeysData[]?

Direct array access is unsupported and breaks internal consistency. The Add* functions handle timestamping, trickle-queue management, and legacy compatibility. According to the Dear ImGui source, these functions are the only supported API for submitting input, ensuring frame-rate independence and correct behavior with ConfigInputTrickleEventQueue.

How do I prevent ImGui from capturing mouse events when clicking through to my game?

After calling ImGui::NewFrame(), check io.WantCaptureMouse. If this flag is false, the mouse is not over any ImGui window or widget, and you should process the event in your application. If true, ImGui owns the input for that frame, and you should skip sending it to your game logic.

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 →