How to Implement Gamepad Navigation Input Handlers in Dear ImGui

Dear ImGui consumes gamepad input through the ImGuiIO interface when the ImGuiConfigFlags_NavEnableGamepad flag is set, requiring the backend to feed button and axis events via io.AddKeyEvent() and io.AddKeyAnalogEvent() each frame.

Dear ImGui’s navigation system is built around the ImGuiIO interface. When you implement gamepad navigation input handlers for ImGui, you translate platform-specific controller events into the ImGuiKey_Gamepad* enumeration and submit them before ImGui::NewFrame(). This guide walks through the exact implementation steps found in the ocornut/imgui source code, referencing core files such as imgui.cpp and the official platform backends.

Enable Gamepad Navigation in ImGuiIO

Before the backend submits any controller data, the application must opt in to gamepad navigation.

Set the Master Navigation Flag

Set the ImGuiConfigFlags_NavEnableGamepad flag in your initialization code:

ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;

Signal Gamepad Presence with Backend Flags

The backend must tell ImGui that a physical controller is available by setting ImGuiBackendFlags_HasGamepad:

io.BackendFlags |= ImGuiBackendFlags_HasGamepad;

If the controller disconnects, clear this flag so ImGui stops waiting for gamepad input.

Digital Button Input Handlers for Gamepad Navigation

Digital buttons are submitted through the ImGuiIO::AddKeyEvent() API. Dear ImGui does not hard-code a physical button layout; you decide which physical button maps to each ImGuiKey_Gamepad* value.

For a standard controller layout, forward face buttons and utility controls like this:

io.AddKeyEvent(ImGuiKey_GamepadFaceDown,  down);   // “A” / “Cross”
io.AddKeyEvent(ImGuiKey_GamepadFaceRight, down);  // “B” / “Circle”
io.AddKeyEvent(ImGuiKey_GamepadFaceLeft,  down);  // “X” / “Square”
io.AddKeyEvent(ImGuiKey_GamepadFaceUp,    down);  // “Y” / “Triangle”

io.AddKeyEvent(ImGuiKey_GamepadStart,      down);
io.AddKeyEvent(ImGuiKey_GamepadBack,       down);
io.AddKeyEvent(ImGuiKey_GamepadLShoulder,  down);
io.AddKeyEvent(ImGuiKey_GamepadRShoulder,  down);

Directional pad buttons are treated the same way:

io.AddKeyEvent(ImGuiKey_GamepadDpadLeft,  down);
io.AddKeyEvent(ImGuiKey_GamepadDpadRight, down);
io.AddKeyEvent(ImGuiKey_GamepadDpadUp,    down);
io.AddKeyEvent(ImGuiKey_GamepadDpadDown,  down);

Analog Axis Input Handlers for Gamepad Navigation

Analog data requires ImGuiIO::AddKeyAnalogEvent(), which accepts both a digital bool and a normalized float magnitude from 0.0 to 1.0. This is critical for the left stick, which drives directional navigation, and for triggers treated as analog buttons.

Apply a dead-zone threshold to eliminate stick drift, then submit the clamped magnitude:

const float dead = 0.2f;

// Left stick X/Y (used for directional navigation)
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft,  leftX < -dead, -leftX);
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, leftX >  dead,  leftX);
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp,    leftY < -dead, -leftY);
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown,  leftY >  dead,  leftY);

// Triggers (treated as analog buttons)
io.AddKeyAnalogEvent(ImGuiKey_GamepadLTrigger, true, leftTrigger);
io.AddKeyAnalogEvent(ImGuiKey_GamepadRTrigger, true, rightTrigger);

Frame Update Cycle for Gamepad Input

Each frame, the backend must refresh the input state before calling ImGui::NewFrame(). According to the reference flow in the ocornut/imgui source, clear the previous frame’s state and push the latest gamepad data:

ImGuiIO& io = ImGui::GetIO();
io.ClearInputKeys();   // start fresh each frame

// ... submit all gamepad buttons and axes ...

ImGui::NewFrame();

How ImGui Translates Input into Navigation Actions

Once the input reaches Dear ImGui, the navigation engine inside imgui.cpp handles the rest. The NavUpdate() function translates submitted keys into focus movement, activation, and cancellation:

  • ImGuiKey_GamepadFaceDown triggers Activate.
  • ImGuiKey_GamepadFaceRight triggers Cancel.
  • ImGuiKey_GamepadLStick left, right, up, and down variants move focus between widgets.
  • The engine automatically applies wrapping, key repeat, and slow-move behavior when the analog stick is held near the dead-zone threshold.

Official Backend Reference Implementations

The ocornut/imgui repository ships with fully functional gamepad handling in its official backends. Inspect these files to see how platform-specific APIs map to ImGuiKey_Gamepad*:

Complete Generic Backend Example

The following pattern works in any custom backend. Call this routine once per frame before ImGui::NewFrame():

void MyBackendUpdateGamepad()
{
    ImGuiIO& io = ImGui::GetIO();
    io.ClearInputKeys();                     // start fresh each frame

    // Detect connected controller
    if (!myGamepad.IsConnected())
        return;                              // no gamepad → nothing to feed

    io.BackendFlags |= ImGuiBackendFlags_HasGamepad;

    // Face and utility buttons
    io.AddKeyEvent(ImGuiKey_GamepadFaceDown,  myGamepad.ButtonDown(MyGamepad::A));
    io.AddKeyEvent(ImGuiKey_GamepadFaceRight, myGamepad.ButtonDown(MyGamepad::B));
    io.AddKeyEvent(ImGuiKey_GamepadFaceLeft,  myGamepad.ButtonDown(MyGamepad::X));
    io.AddKeyEvent(ImGuiKey_GamepadFaceUp,    myGamepad.ButtonDown(MyGamepad::Y));

    io.AddKeyEvent(ImGuiKey_GamepadLShoulder, myGamepad.ButtonDown(MyGamepad::LB));
    io.AddKeyEvent(ImGuiKey_GamepadRShoulder, myGamepad.ButtonDown(MyGamepad::RB));
    io.AddKeyEvent(ImGuiKey_GamepadStart,    myGamepad.ButtonDown(MyGamepad::Start));
    io.AddKeyEvent(ImGuiKey_GamepadBack,     myGamepad.ButtonDown(MyGamepad::Back));

    // D-Pad
    io.AddKeyEvent(ImGuiKey_GamepadDpadLeft,  myGamepad.ButtonDown(MyGamepad::DPadLeft));
    io.AddKeyEvent(ImGuiKey_GamepadDpadRight, myGamepad.ButtonDown(MyGamepad::DPadRight));
    io.AddKeyEvent(ImGuiKey_GamepadDpadUp,    myGamepad.ButtonDown(MyGamepad::DPadUp));
    io.AddKeyEvent(ImGuiKey_GamepadDpadDown,  myGamepad.ButtonDown(MyGamepad::DPadDown));

    // Analog sticks with dead-zone
    const float dead = 0.2f;
    float lx = myGamepad.Axis(MyGamepad::LeftX);   // -1.0 … +1.0
    float ly = myGamepad.Axis(MyGamepad::LeftY);

    if (lx < -dead) io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft,  true, -lx);
    if (lx >  dead) io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, true,  lx);
    if (ly < -dead) io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp,    true, -ly);
    if (ly >  dead) io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown,  true,  ly);

    // Triggers
    float lt = myGamepad.Axis(MyGamepad::LT);   // 0.0 … 1.0
    float rt = myGamepad.Axis(MyGamepad::RT);
    io.AddKeyAnalogEvent(ImGuiKey_GamepadLTrigger, true, lt);
    io.AddKeyAnalogEvent(ImGuiKey_GamepadRTrigger, true, rt);
}

After this function returns and you invoke ImGui::NewFrame(), the Dear ImGui navigation system will automatically move focus, show the navigation cursor, and allow widget activation with the gamepad.

Important Configuration Details

  • Dead-zone handling — Keep a small threshold (≈ 0.2) when calling AddKeyAnalogEvent to avoid drift from idle sticks.
  • Analog versus digital — Submit true buttons with AddKeyEvent; submit thumb-sticks and triggers with AddKeyAnalogEvent.
  • Swap confirm and cancel — Set io.ConfigNavSwapGamepadButtons = true if your target platform uses a Nintendo-style layout where the east face button cancels and the south face button activates.
  • Single controller limit — Dear ImGui consumes only the first detected controller. To switch active gamepads, manage the connection state in your backend and toggle ImGuiBackendFlags_HasGamepad to reflect which controller is currently active.

Summary

  • Gamepad navigation requires setting ImGuiConfigFlags_NavEnableGamepad in ImGuiIO.
  • The backend must set ImGuiBackendFlags_HasGamepad when a controller is connected.
  • Digital buttons map to ImGuiKey_Gamepad* values through io.AddKeyEvent().
  • Analog axes and triggers use io.AddKeyAnalogEvent() with a 0.0 to 1.0 magnitude and an applied dead-zone.
  • Feed input and clear state each frame before ImGui::NewFrame() so the NavUpdate() routine in imgui.cpp can resolve focus moves, activation, and cancellation.

Frequently Asked Questions

What is the difference between AddKeyEvent and AddKeyAnalogEvent for gamepad navigation?

AddKeyEvent reports a pure digital on/off state for buttons such as the face buttons, shoulders, and D-pad directions. AddKeyAnalogEvent reports both a digital crossing state and a normalized 0.0 to 1.0 magnitude for sources like thumb-sticks and triggers, which lets ImGui apply slow-move and repeat behaviors.

How should I handle analog stick dead-zones when feeding gamepad data to ImGui?

Compare the raw axis value against a small threshold such as 0.2. Only call AddKeyAnalogEvent when the magnitude exceeds that dead-zone, and pass the unsigned residual magnitude as the analog value. This prevents phantom navigation from stick drift.

Can I swap the confirm and cancel buttons to match a Nintendo controller layout?

Yes. Set io.ConfigNavSwapGamepadButtons = true before NewFrame. When enabled, Dear ImGui swaps the default activation and cancel assignments so that ImGuiKey_GamepadFaceRight activates and ImGuiKey_GamepadFaceDown cancels.

Does Dear ImGui support navigation from multiple gamepads at the same time?

No. The navigation engine in imgui.cpp consumes input from a single logical gamepad represented by the ImGuiBackendFlags_HasGamepad flag. If you need to support hot-swapping between multiple controllers, manage the connection state in your backend and toggle the flag to reflect which controller is currently active.

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 →