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*:
- Win32 —
backends/imgui_impl_win32.cppmapsXINPUT_GAMEPADbits toImGuiKey_Gamepad*via theMAP_BUTTONmacro around lines 360-398. - GLFW —
backends/imgui_impl_glfw.cppforwards SDL-style gamepad buttons using theMAP_BUTTONmacro around lines 904-934. - SDL2 —
backends/imgui_impl_sdl2.cppimplementsImGui_ImplSDL2_AddGamepadEventaround lines 770-795. - SDL3 —
backends/imgui_impl_sdl3.cppcontains analogous SDL3 handling around lines 770-798. - Android —
backends/imgui_impl_android.cppforwards Android gamepad keys around lines 172-190.
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
AddKeyAnalogEventto avoid drift from idle sticks. - Analog versus digital — Submit true buttons with
AddKeyEvent; submit thumb-sticks and triggers withAddKeyAnalogEvent. - Swap confirm and cancel — Set
io.ConfigNavSwapGamepadButtons = trueif 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_HasGamepadto reflect which controller is currently active.
Summary
- Gamepad navigation requires setting
ImGuiConfigFlags_NavEnableGamepadinImGuiIO. - The backend must set
ImGuiBackendFlags_HasGamepadwhen a controller is connected. - Digital buttons map to
ImGuiKey_Gamepad*values throughio.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 theNavUpdate()routine inimgui.cppcan 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →