Why the page looks empty until you press something
Almost every support thread about web gamepad testers starts the same way: it says no
controller detected, but it is right here. Nothing is wrong. Browsers deliberately
refuse to list a connected pad until you use it. The Gamepad specification carries a note
to exactly that effect — that getGamepads() returns an empty list before a
gamepad user gesture has been seen, and that the reason is fingerprinting. The set of
controllers on your desk, complete with their id strings, is a startlingly
good identifier; making a page earn it with a deliberate press is cheap protection.
The specification is careful about what counts as that gesture: a button only qualifies if it has a neutral resting state and has been seen released at least once, and axis movement only qualifies past a threshold the browser chooses — one the specification says should be large enough that random jitter does not count. The flip side is the part worth acting on. A controller that fills this page in while sitting untouched on your desk has produced movement past that bar on its own. Take that seriously and run the stick drift test.
Positions, not letters: how the index map really works
The single most useful thing to understand about the Gamepad API is that it numbers places on a controller, not the letters printed on the buttons. The specification defines one canonical layout, the Standard Gamepad, and describes each index purely geometrically: index 0 is the "Bottom button in right cluster", index 12 is the "Top button in left cluster", and so on around the pad. No vendor name appears anywhere in that table.
| Index | Position, in the specification's own words | Xbox | DualSense (PS5) | Switch Pro |
|---|---|---|---|---|
| buttons[0] | Bottom button in right cluster | A | Cross ✕ | B |
| buttons[1] | Right button in right cluster | B | Circle ○ | A |
| buttons[2] | Left button in right cluster | X | Square □ | Y |
| buttons[3] | Top button in right cluster | Y | Triangle △ | X |
| buttons[4] | Top left front button | LB | L1 | L |
| buttons[5] | Top right front button | RB | R1 | R |
| buttons[6] | Bottom left front button | LT | L2 | ZL |
| buttons[7] | Bottom right front button | RT | R2 | ZR |
| buttons[8] | Left button in center cluster | View | Create | Minus − |
| buttons[9] | Right button in center cluster | Menu | Options | Plus + |
| buttons[10] | Left stick pressed button | LS (left stick click) | L3 | Left stick click |
| buttons[11] | Right stick pressed button | RS (right stick click) | R3 | Right stick click |
| buttons[12] | Top button in left cluster | D-pad ↑ | D-pad ↑ | D-pad ↑ |
| buttons[13] | Bottom button in left cluster | D-pad ↓ | D-pad ↓ | D-pad ↓ |
| buttons[14] | Left button in left cluster | D-pad ← | D-pad ← | D-pad ← |
| buttons[15] | Right button in left cluster | D-pad → | D-pad → | D-pad → |
| buttons[16] | Center button in center cluster | Xbox button | PS button | HOME |
| Index | Position, in the specification's own words | −1 means | +1 means |
|---|---|---|---|
| axes[0] | Horizontal axis for left stick (negative left/positive right) | left | right |
| axes[1] | Vertical axis for left stick (negative up/positive down) | up | down |
| axes[2] | Horizontal axis for right stick (negative left/positive right) | left | right |
| axes[3] | Vertical axis for right stick (negative up/positive down) | up | down |
Read the first four rows across and you have the reason half the internet's controller
guides contradict each other. The physical bottom button of the right cluster is labelled
A on an Xbox pad, Cross on a PlayStation pad — and B on a Switch Pro
Controller, because Nintendo arranges its letters the other way round. A game that reads
buttons[0] and prints "press A" is correct on Xbox and actively wrong on
Switch. The controller button mapping page works
through the consequences, including what browsers do about it.
What mapping tells you before you trust any of this
The table above only applies when your controller reports mapping === "standard".
That property is an enumeration with exactly three legal values, and the device panel in
the tool prints yours verbatim.
| Value | What the specification says | What it means for you |
|---|---|---|
| "standard" | The user agent has remapped the device onto the Standard Gamepad layout, so the index table on this page applies. | Indices are portable. A game that reads buttons[0] gets the bottom face button on every pad reporting this value. |
| "" (empty string) | The spec states plainly: “The empty string indicates that no mapping is in use for this gamepad.” | Indices are whatever the device reports in its own HID order. The table on this page does not apply, and neither does anyone else’s. Read the labels from the tester, not from a chart. |
| "xr-standard" | Reserved for WebXR input sources, and never for ordinary game controllers. | getGamepads() must never report it — the specification says so outright. XR controllers reach a page through WebXR instead, not through this API. |
An empty string is the case worth knowing about, because it is silent. Generic USB pads, arcade sticks, flight gear, older third-party controllers and anything reached through an adapter frequently land here. Nothing errors; the buttons simply arrive in whatever order the device's own HID descriptor lists them, which might put Start at index 9 or at index 6. When the tool shows an empty mapping it says so in an amber panel, and the correct response is to stop reading charts — including this one — and read your own labels off the live panel instead.
The right bumper that never flashes cyan
Suppose the right bumper feels unreliable. Open the live panel and press every button in
order around the pad. 16 of them flash cyan;
buttons[5] never does, and its value stays at 0.00. That is a clean negative
result — the browser received nothing at
all — which puts the fault in the switch, its solder joint, or the ribbon between the
board and the shoulder assembly. Now the counter-case, which looks similar and means the
opposite: buttons[5] does light, holds a value of 1.00 while you
hold it, and your game still ignores it. The hardware is fine and the binding is not; no
amount of further hardware testing will help.
There is a third case unique to triggers, and it is why they have their own page. On many pads the analog triggers register as moving long before they register as pressed, so a trigger can look dead in a simple pass/fail tester while the value bar is clearly climbing. The analog trigger test shows the continuous value and the pressed flag next to each other, with the exact threshold Chromium applies between them.
How fast can a web page actually see your controller?
Three rate limits sit in series between the switch under your thumb and this page: the controller's own report rate, the rate the browser samples it at, and the rate the page reads the browser's snapshot. Any measurement can only ever reveal the slowest of the three, and the middle one is usually it. Chromium runs a dedicated gamepad polling thread with its interval set to 4 ms — the constant is commented "~250 Hz" in the browser's own source — so a 1000 Hz controller cannot present as 1000 Hz to any web page in that browser, no matter how the page is written.
This is why the update-rate meter in the tool is labelled the way it is. It reports how
often your browser handed this page a snapshot carrying a new
timestamp, and it does not call that number your controller's polling rate,
because it is not. Testers that print "your polling rate: 250 Hz" are usually printing the
browser's sampling interval back at you with the controller's name attached.
Dead zones, drift, and the difference between them
A thumbstick almost never reports exactly zero at rest, and games hide that behind a dead zone: a region around the centre whose inputs are forced to zero. Drift is what happens when the resting offset grows until it escapes the dead zone and the camera starts moving on its own. The two are inseparable — a drift figure means nothing without knowing what dead zone it has to clear — which is why this site treats them as one subject split over two pages. The dead-zone test lets you drag a threshold across your own live stick input and watch what survives; the stick drift test samples your untouched sticks for a minute and reports the largest offset they produced.
Both pages carry generated arithmetic; none of the numbers were borrowed. The dead-zone tables are computed from the geometry of the unit disc — how much travel each shape removes, how far a square dead zone reaches on the diagonals, what a scaled response returns at each input magnitude — and every published cell is checked against the same functions the tool uses when the site is built.
What this tester cannot tell you
- It cannot see inputs the controller keeps to itself: pairing buttons, most profile switches, and on many pads the gyroscope and touchpad surface.
- It cannot separate the controller from the cable, the adapter or the port. Swap one variable at a time; retest wired if you tested wireless.
- It cannot read your device's true USB polling rate, for the reason above.
- It cannot prove a stick is healthy from an all-zero drift run, because an upstream dead zone produces the identical result.
- It cannot promise that a pad reporting an empty
mappingmatches any published index table, including this site's.
Common reasons people run this test
- A second-hand purchase. Two minutes here covers every button, both sticks and both triggers before the return window closes — the Xbox, DualSense, DualShock 4 and Switch Pro pages open with the right labels already applied.
- Suspected drift after a game started creeping — measure before buying replacement modules.
- Warranty evidence. A screenshot showing a resting offset with the controller untouched is more persuasive than a description.
- Building something. If you are writing a game or a web app, the index table and the
mappingcaveat are the two things worth getting right on day one.