The complete index table
The Position column is quoted from the button table in the W3C Gamepad specification, and the Chromium column gives the name the browser's own code uses for the same index — declared in the same order, which is how you can see the two documents agree. The four controller columns are this site's contribution; the specification never names a vendor.
| Index | Position, in the specification's own words | Chromium's name | Xbox | DualSense (PS5) | DualShock 4 (PS4) | Switch Pro |
|---|---|---|---|---|---|---|
| buttons[0] | Bottom button in right cluster | BUTTON_INDEX_PRIMARY | A | Cross ✕ | Cross ✕ | B |
| buttons[1] | Right button in right cluster | BUTTON_INDEX_SECONDARY | B | Circle ○ | Circle ○ | A |
| buttons[2] | Left button in right cluster | BUTTON_INDEX_TERTIARY | X | Square □ | Square □ | Y |
| buttons[3] | Top button in right cluster | BUTTON_INDEX_QUATERNARY | Y | Triangle △ | Triangle △ | X |
| buttons[4] | Top left front button | BUTTON_INDEX_LEFT_SHOULDER | LB | L1 | L1 | L |
| buttons[5] | Top right front button | BUTTON_INDEX_RIGHT_SHOULDER | RB | R1 | R1 | R |
| buttons[6] | Bottom left front button | BUTTON_INDEX_LEFT_TRIGGER | LT | L2 | L2 | ZL |
| buttons[7] | Bottom right front button | BUTTON_INDEX_RIGHT_TRIGGER | RT | R2 | R2 | ZR |
| buttons[8] | Left button in center cluster | BUTTON_INDEX_BACK_SELECT | View | Create | Share | Minus − |
| buttons[9] | Right button in center cluster | BUTTON_INDEX_START | Menu | Options | Options | Plus + |
| buttons[10] | Left stick pressed button | BUTTON_INDEX_LEFT_THUMBSTICK | LS (left stick click) | L3 | L3 | Left stick click |
| buttons[11] | Right stick pressed button | BUTTON_INDEX_RIGHT_THUMBSTICK | RS (right stick click) | R3 | R3 | Right stick click |
| buttons[12] | Top button in left cluster | BUTTON_INDEX_DPAD_UP | D-pad ↑ | D-pad ↑ | D-pad ↑ | D-pad ↑ |
| buttons[13] | Bottom button in left cluster | BUTTON_INDEX_DPAD_DOWN | D-pad ↓ | D-pad ↓ | D-pad ↓ | D-pad ↓ |
| buttons[14] | Left button in left cluster | BUTTON_INDEX_DPAD_LEFT | D-pad ← | D-pad ← | D-pad ← | D-pad ← |
| buttons[15] | Right button in left cluster | BUTTON_INDEX_DPAD_RIGHT | D-pad → | D-pad → | D-pad → | D-pad → |
| buttons[16] | Center button in center cluster | BUTTON_INDEX_META | Xbox button | PS 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 |
The Nintendo problem, stated precisely
Nintendo prints its face-button letters in the opposite arrangement to Microsoft. Because
the API numbers positions, the letters move with the position rather than the index. On an
Xbox controller the button marked A is buttons[0]; on a Switch Pro
Controller the button marked A is buttons[1]. B is
buttons[1] on Xbox and buttons[0] on the Pro
Controller. The two pads print the same thing at exactly 4 of the
17 canonical indices, and all 4 are the d-pad
directions (D-pad ↑, D-pad ↓, D-pad ←, D-pad →).
This is not the browser being sloppy — it is the browser being consistent. Chromium's
Nintendo mapping code assigns BUTTON_INDEX_PRIMARY from the controller's
button_b field, and the neighbouring array literal comments the four entries
"// B button", "// A button", "// Y button", "// X button" in that order. The browser has
decided, correctly, that the position is what the index means. The consequence for anyone
shipping software is blunt: never print a face-button letter from an index
unless you have first read the controller's id and decided which letter set
applies. The Switch Pro controller test shows
the source that settles it, and the Xbox controller
tester is the other half of the comparison if you have both pads to hand.
Where the two PlayStation pads disagree
The DualSense and the DualShock 4 are the easy case. Across all 17
canonical indices they differ at exactly 1:
buttons[8], the left button in center cluster, where
Sony renamed the DualShock 4's Share button to
Create on the DualSense. The position, the index and the
behaviour are identical; only the printing changed. Code written against indices needs no
changes between the two generations, which is precisely the payoff the canonical layout
exists to deliver. The PS4 controller test takes that
further and checks the browser's own mapper source for both pads.
Index 17: the buttons that do not fit
The Standard Gamepad ends at 16, and real controllers kept adding buttons afterwards. Chromium handles this by appending the extra input immediately after the canonical set, so on each of these devices the extra button lands at index 17. The enum names below are the identifiers in Chromium's source, and the last column names the mapper files where the assignment is actually written — which is not the same question as where the name is declared.
| Index | Controller | Button | Chromium enum | Assigned in |
|---|---|---|---|---|
| buttons[17] | Xbox Series X|S controller | Share | XBOX_SERIES_X_BUTTON_SHARE | gamepad_standard_mappings_mac.mm, gamepad_standard_mappings_linux.cc |
| buttons[17] | Switch Pro Controller | Capture | SWITCH_PRO_BUTTON_CAPTURE | gamepad_standard_mappings_win.cc, gamepad_standard_mappings_mac.mm, gamepad_standard_mappings_linux.cc |
| buttons[17] | DualSense (PS5) | Touchpad click | DUAL_SENSE_BUTTON_TOUCHPAD | gamepad_standard_mappings_win.cc, gamepad_standard_mappings_mac.mm, gamepad_standard_mappings_linux.cc |
| buttons[17] | DualShock 4 (PS4) | Touchpad click | DUALSHOCK_BUTTON_TOUCHPAD | gamepad_standard_mappings_win.cc, gamepad_standard_mappings_mac.mm, gamepad_standard_mappings_linux.cc |
The last column is where the surprise lives. Three of these four are assigned on all three desktop platforms; the Xbox Share button is assigned only by the macOS and Linux mappers, in a function that runs after the ordinary Xbox Bluetooth mapper and raises the reported button count. Chromium's Windows mapper has no equivalent row, so a Series X|S pad does not report the same button count everywhere. The Linux file does not even reuse the shared name — it declares its own constant at the same value — which is exactly why the column says where the assignment happens instead of leaving you to infer it from an enum.
Notice too that all four rows share an index. Index 17 alone tells you
nothing about which button was pressed — it is a touchpad click on one pad and a capture
button on another. The only way to disambiguate is the controller's id
string, which the device panel in the tool prints in full. This also explains a reading you
may already have seen: a pad reporting 18 buttons where you
expected 17 is not broken and is not "extra standard"; it is a standard
pad plus one appended extra.
When the table does not apply at all
Everything above is conditional on your controller reporting
mapping === "standard". The property is an enumeration with three legal
values and no others.
| Value | Specification | Consequence |
|---|---|---|
| "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. |
If yours reports the empty string, the fastest route to a correct mapping is to build your own in about thirty seconds: open the live panel, press each button in a fixed order, and write down the index that lights. That list is authoritative for your device in a way no published chart can be. It is also worth repeating the exercise if you use the pad through an adapter, because the adapter — not the controller — is what the browser identifies, and two adapters for the same controller can produce two different orderings.
Advice for people writing code against this
- Bind by index, label by
id. Readbuttons[0], but decide whether to draw "A", "Cross" or "B" from the device string. - Check
mappingbefore trusting an index. One equality test separates the devices your layout describes from the ones it does not. - Do not assume 17. Iterate
buttons.length; extras start at index 17, are device-specific, and on the Xbox Series X|S are platform-specific as well. - Remember the vertical axes point down.
axes[1]andaxes[3]are positive downward, which the specification states outright and which inverts roughly half of all first drafts. - Read
value, notpressed, at indices 6 and 7. The flag is a convenience with a browser-chosen threshold baked into it; apply your own trigger dead zone and let the player set it. And do not assume those two indices are analog at all — the analog trigger test shows what a digital one looks like. - Offer a remap screen anyway. Every assumption above fails for some real device; a rebinding UI is the only complete answer.
Checking your own pad against the table
The live panel above shows each canonical index with its value, so you can verify the
table against your hardware in a minute. Press the bottom face button and confirm index 0
lights. Push the left stick right and confirm axes[0] goes positive; push it
down and confirm axes[1] goes positive too — that is the one that surprises
people. Then squeeze each trigger slowly and watch buttons[6] and
buttons[7] climb continuously, with no snap straight to 1, which is the subject
of the analog trigger test. If any of these disagree
with the table, check mapping first — it explains the overwhelming majority
of mismatches.