Web Gamepad API: DirectInput, XInput, and the mapping confusion

What the Gamepad API gives you

The browser's Gamepad API exposes connected controllers through navigator.getGamepads(), which returns an array of Gamepad objects. Each object has a buttons array — every entry is an object with pressed, touched, and an analogue value from 0 to 1 — and an axes array holding analogue stick positions, each running from -1 to 1. There are gamepadconnected and gamepaddisconnected events, but button and axis state is never pushed to you: you have to poll it.

In practice that means reading the gamepad inside a requestAnimationFrame loop, once per frame, and comparing this frame against the last to detect a button that was just pressed or released. The object you get is a frozen snapshot, so you call getGamepads() again every frame for fresh values. This polling model is the first thing that surprises developers coming from keyboard or pointer events.

The 'standard mapping'

The specification defines a canonical layout called the standard mapping. When a controller matches it, gamepad.mapping is the string standard, and the indices are fixed and predictable: buttons[0] is the bottom face button (A / cross), buttons[1] the right one (B / circle), and the bumpers, triggers, d-pad, and stick clicks all sit at defined positions, with axes[0] to axes[3] carrying the left and right sticks. Seventeen buttons and four axes in a known order.

When gamepad.mapping is an empty string, the controller did not match the standard layout and none of those indices are guaranteed. The same physical button can land at a different index, a trigger can appear as an axis, and the d-pad can be a single hat axis instead of four buttons. The mapping property is the single most important field to check before you trust any index.

DirectInput vs XInput: the root of the confusion

On Windows there are two ways a game talks to a controller. XInput is Microsoft's modern API, built around the Xbox controller and the layout every Xbox-compatible pad follows. Because that layout is fixed, browsers can map an XInput device cleanly onto the standard mapping — which is why an Xbox controller 'just works' in a web game.

DirectInput is the older, generic API for any HID (human interface device) controller: many third-party pads, arcade sticks, flight sticks, racing wheels, and older or no-name controllers. DirectInput imposes no fixed button order, so the browser has no canonical layout to map onto and reports an empty mapping. That single difference is the source of nearly every 'my buttons are scrambled in the browser' report.

Why the same button reports different indices

With a DirectInput pad, the manufacturer decides which physical control is button 1, so your A button might arrive as buttons[2], the triggers might show up inside the axes array rather than as buttons, and the d-pad might be reported as a point-of-view (POV) hat — one axis with eight directions — instead of four separate buttons.

To make these devices usable, each browser applies its own remapping heuristics, and they do not always agree: the same DirectInput controller can produce different indices in Chrome and Firefox. XInput sidesteps the whole problem because Microsoft fixed the layout, which is why 'use an Xbox-style controller' is the most reliable advice for web games.

Handling it in code

The robust pattern is to check gamepad.mapping first. If it equals standard, you can safely use named indices. If it is empty, do not hardcode anything — offer a remapping screen that asks the player to press each action so you learn that device's layout, and save the result keyed by gamepad.id so it persists per controller.

Two more details matter regardless of mapping. Analogue sticks rarely rest at exactly zero, so apply a small dead zone (commonly around 0.1) and treat anything inside it as no input, or drift will move the character on its own. And because the API is poll-based, read the gamepad fresh each frame and diff against the previous frame to turn continuous state into discrete 'just pressed' events. The gamepad tester on this site shows the raw button indices and axis values your specific device reports — the fastest way to see whether it is standard-mapped.

Takeaways

Always check mapping before trusting indices. Standard means the spec layout; empty means device-specific.

Prefer XInput / Xbox-style controllers for web games — they map to the standard layout, while DirectInput pads do not.

Never hardcode indices for non-standard pads. Provide a remapping UI and store the result per gamepad.id.

Apply a dead zone to axes so resting sticks do not register as movement.

Poll every frame and diff — for buttons and axes the Gamepad API gives you state, not events.