# Gamepads (/spud-api/api-reference/gamepads)



{/* source: packages/api/readme.md:224-499 */}

## `gamepads` API Reference [#gamepads-api-reference]

### Player controllers [#player-controllers]

Each player (`gamepads.p1`–`gamepads.p4`) provides:

* `index` - `0` | `1` | `2` | `3`
* `gamepad` - Raw [Gamepad](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad) object
* To detect layout/model/vendor/product from the browser `Gamepad.id&#x60; string, call &#x2A;*`identifyGamepad`** (exported from `@spud.gg/api`) so apps that don't need identification can tree-shake the large internal ID list. For a connected player: `identifyGamepad(player.gamepad?.id ?? '')`. It returns:
  * `layout` – `GamepadBaseLayout` (`Nintendo` | `PlayStation` | `Xbox` | `Generic`)
  * `generalModel` – `GamepadModel` (generic model enum such as `Xbox360`, including for many off-brand pads)
  * `vendorId` / `productId` – Hex strings parsed from the gamepad `id` when known
  * `productName` – Human-readable product string when known, otherwise `'Unknown'`
* `justConnected` - `true` if the gamepad just connected this frame
* `justDisconnected` - `true` if the gamepad just disconnected this frame
* `isDown(button)` - Checks if a button is currently pressed
* `wasDown(button)` - Checks if a button was pressed in the previous frame
* `justPressed(button)` - Checks if a button was just pressed (current frame && not previous)
* `justReleased(button)` - Checks if a button was just released (previous frame && not current)
* `buttonHeld(button)` - Checks if a button is being held (pressed in both current and previous frame)
* `buttonsDown` - A `Set` of all currently pressed buttons
* `previousButtonsDown` - A `Set` of all buttons pressed in the previous frame
* `leftStick`/`rightStick` - Analog stick accessors with properties:
  * `x`/`y` - Normalized values that account for deadzones
  * `rawX`/`rawY` - Raw axis values directly from the gamepad
  * `magnitude` - Distance from center of the analog stick (0 to 1)
  * `angle` - Angle in radians (-π to π)
  * `angleDegrees` - Angle in degrees (0 to 360), clockwise from right
  * `cardinal` - Nearest cardinal direction (N/NE/E/SE/S/SW/W/NW)
  * `snap4` - Normalized values that snap to 4-way directions (up/down/left/right)
  * `snap8` - Normalized values that snap to 8-way directions (including diagonals)
  * `deadzone` - The amount of deadzone for the stick
  * `motion(options?)` - Detect directional motion with hysteresis to prevent jittery state changes
    * `options.activate` - Threshold for activating motion (0-1, default: 0.75)
    * `options.deactivate` - Threshold for deactivating motion (0-1, default: 0.25)
    * `options.diagonals` - If true, returns 8-way directions; if false, returns 4-way (default: false)
    * Returns (4-way): `{ justMoved: { Up, Down, Left, Right }, direction: { x, y } }`
    * Returns (8-way): `{ justMoved: { N, NE, E, SE, S, SW, W, NW }, direction: CardinalDirection | 'Neutral' }`
* `leftTrigger`/`rightTrigger` - Trigger accessors with properties:
  * `value` - Normalized value that accounts for deadzone
  * `rawValue` - Raw analog button value directly from the gamepad
  * `deadzone` - The amount of deadzone for the trigger
  * `rumble(duration?, intensity?)` - Method to provide haptic feedback to the trigger
    * `duration`: number - Duration in milliseconds (optional, default: `30`)
    * `intensity`: HapticIntensity | number - Intensity of the rumble (optional, default: `Balanced`)
* `rumble(duration?, intensity?)` - Easy vibration effect
  * `duration`: number - Duration in milliseconds (optional, default: `30`)
  * `intensity`: HapticIntensity - Intensity of the rumble (optional, default: `Balanced`)
* `haptic({ duration?, strong?, weak? })` - Fine-grained [haptic](https://developer.mozilla.org/en-US/docs/Web/API/GamepadHapticActuator) control
  * `duration`: number - Duration of haptic effect in milliseconds (optional, default: `0`)
  * `strong`: number - Intensity of strong actuator (`0`-`1`) (optional, default: `0`)
  * `weak`: number - Intensity of weak actuator (`0`-`1`) (optional, default: `0`)
* `hapticReset()` - Cancels any ongoing haptic feedback

#### Examples [#examples]

```ts
// check if a button is currently pressed
if (gamepads.p1.isDown(Button.South)) {
  console.log('A/✕ button is being pressed');
}

// check for buttons that were just pressed this frame
if (gamepads.p2.justPressed(Button.North)) {
  console.log('Y/△ button was just pressed');
}

// using analog stick values with deadzone handling
const { x, y } = gamepads.p1.leftStick;
yourGame.moveCharacter(x, y); // values are normalized with deadzone applied

// using analog stick with 8-way directional snapping
const { x: snapX, y: snapY } = gamepads.p1.rightStick.snap8;
yourGame.aimWeapon(snapX, snapY);

// using analog stick geometric properties
const { magnitude, angle, cardinal } = gamepads.p1.leftStick;
if (magnitude > 0.8) yourGame.sprint();
yourGame.rotateCamera(angle);
if (cardinal === 'NE') yourGame.openNorthEastDoor();

// using motion detection for menu navigation
const { justMoved } = gamepads.p1.leftStick.motion();
if (justMoved[Motion.Up]) yourGame.selectPreviousMenuItem();
if (justMoved[Motion.Down]) yourGame.selectNextMenuItem();

// using 8-way motion for aiming with custom activation/deactivation thresholds
const { direction, didJustMove } = gamepads.p1.rightStick.motion({
  diagonals: true, // 8-way
  activate: 0.5,
  deactivate: 0.2,
});
if (didJustMove) {
  yourGame.aimWeapon(direction);
}

// using trigger values
if (gamepads.p1.rightTrigger.value > 0.8) {
  yourGame.fireWeapon();

  // apply haptic feedback to the trigger
  gamepads.p1.rightTrigger.rumble(100, HapticIntensity.Heavy);
}

// apply general haptic feedback
gamepads.p1.rumble(200, HapticIntensity.Balanced);

// custom haptic control
gamepads.p1.haptic({
  duration: 300,
  strong: 0.7,
  weak: 0.3,
});

gamepads.p1.leftTrigger.rumble();
gamepads.p1.rightTrigger.rumble(50, HapticIntensity.Heavy);

// cancel all haptic effects
gamepads.p1.hapticReset();
```

### Constants and types [#constants-and-types]

* **`Button`** - Button constants that map to their [index in the standard mapping](https://w3c.github.io/gamepad/#remapping). eg. DpadUp, Start, Select, LeftShoulder, North/South/East/West (maps to Xbox Y/A/X/B or PlayStation △/✕/□/○ or Nintendo X/B/A/Y), etc.
* **`Axis`** - Gamepad axes -- `Axis.LeftStickX`, `Axis.LeftStickY`, `Axis.RightStickX`, `Axis.RightStickY`
* **`Player`** - `Player.P1`, `Player.P2`, `Player.P3`, `Player.P4`
* **`GamepadBaseLayout`** - Controller base layouts -- `GamepadBaseLayout.Xbox`, `GamepadBaseLayout.PlayStation`, `GamepadBaseLayout.Nintendo`, `GamepadBaseLayout.Generic`
* **`GamepadModel`** - Common controller models, which several specific products can map to -- `GamepadModel.Xbox360`, `GamepadModel.PlayStationDualSense`, `GamepadModel.SwitchPro`, `GamepadModel.SteamDeck`, etc.
* **`identifyGamepad(rawId: string)`** - Maps a browser `Gamepad.id` string to `layout`, `generalModel`, `vendorId`, `productId`, and `productName`. Import only when you need identification so bundlers can omit the ID database otherwise.
* **`HapticIntensity`** - `HapticIntensity.Light`, `HapticIntensity.Balanced`, `HapticIntensity.Heavy`
* **`Motion`** - Directional movement -- `Motion.Up`, `Motion.Down`, `Motion.Left`, `Motion.Right`
* **`CardinalDirection`** - 8-way directions for joystick movements -- `CardinalDirection.N`, `CardinalDirection.NE`, `CardinalDirection.S`, etc.

#### Examples [#examples-1]

```ts
import {
  CardinalDirection,
  GamepadBaseLayout,
  GamepadModel,
  Button,
  HapticIntensity,
  Motion,
  gamepads,
  identifyGamepad,
} from '@spud.gg/api';

// button constants
if (gamepads.p1.justPressed(Button.South)) yourGame.jump();
if (gamepads.p1.isDown(Button.DpadLeft)) yourGame.moveLeft();

// haptic intensity levels
gamepads.p1.rumble(100, HapticIntensity.Light);
gamepads.p1.rumble(100, HapticIntensity.Balanced);
gamepads.p1.rumble(100, HapticIntensity.Heavy);

// gamepad identification (optional import — omit if you don't need layout/model detection)
const info = identifyGamepad(gamepads.p1.gamepad?.id ?? '');
if (info.layout === GamepadBaseLayout.Nintendo) {
  // probably flip A/B and X/Y
}
if (info.generalModel === GamepadModel.PlayStationDualSense) {
  // show touchpad in controller layout
}

// motion and direction constants (4-way)
const { direction, didJustMove } = gamepads.p1.leftStick.motion();
if (direction.x === Motion.Left) yourGame.moveLeft();
if (direction.y === Motion.Up) yourGame.moveUp();

// cardinal directions for 8-way movement
const { direction: cardinalDir } = gamepads.p1.leftStick.motion({ diagonals: true });
if (cardinalDir === CardinalDirection.NE) yourGame.moveNorthEast();
```

### Player groups [#player-groups]

utilities for handling multiple players at once.

#### `anyPlayer` [#anyplayer]

* `isDown(button)` - Checks if any connected player is pressing the button
* `wasDown(button)` - Checks if any connected player was pressing the button in the previous frame
* `justPressed(button)` - Checks if any connected player just pressed the button
* `justReleased(button)` - Checks if any connected player just released the button
* `buttonHeld(button)` - Checks if any connected player is holding the button

#### `connectedPlayers` [#connectedplayers]

* `list` - Array of only the connected players (gamepads that actually exist)
* `isDown(button)` - Checks if all connected players are pressing the button
* `buttonHeld(button)` - Checks if all connected players are holding the button
* `haptic({ duration?, strong?, weak? })` - Apply haptic feedback to all connected players
  * `duration`: number - Duration of haptic effect in milliseconds (optional, default: `0`)
  * `strong`: number - Intensity of strong actuator (`0`-`1`) (optional, default: `0`)
  * `weak`: number - Intensity of weak actuator (`0`-`1`) (optional, default: `0`)
* `rumble(duration?, intensity?)` - Applies rumble effect to all connected players
  * `duration`: number - Duration in milliseconds (optional, default: `30`)
  * `intensity`: HapticIntensity - Intensity of the rumble (optional, default: `Balanced`)
* `leftTrigger.rumble(duration?, intensity?)` - Trigger rumble for all connected players
  * `duration`: number - Duration in milliseconds (optional, default: `30`)
  * `intensity`: HapticIntensity - Intensity of the rumble (optional, default: `Balanced`)
* `rightTrigger.rumble(duration?, intensity?)` - Trigger rumble for all connected players
  * `duration`: number - Duration in milliseconds (optional, default: `30`)
  * `intensity`: HapticIntensity - Intensity of the rumble (optional, default: `Balanced`)

#### `singlePlayer` [#singleplayer]

`gamepads.singleplayer` is a reference to the most recently active gamepad's player. So if `gamepads.p2` was the most recently active player, `gamepads.singlePlayer === gamepads.p2`. This allows you to easily create single-player games without needing to worry about configuring each controller. The player can seamlessly switch between any connected controllers, or connect a new one and continue playing without any additional setup. Anyone can pick up any connected controller and immediately start playing.

#### Examples with player groups [#examples-with-player-groups]

```ts
import { Button, gamepads, HapticIntensity, identifyGamepad, spud } from '@spud.gg/api';

// start the game when all players press start
if (gamepads.connectedPlayers.isDown(Button.Start)) {
  yourGame.startGame();
}

// charge a special move when all players press a button simultaneously
if (gamepads.connectedPlayers.buttonHeld(Button.South)) {
  yourGame.chargeTeamSpecialMove();
}

// apply haptic feedback to all connected players
gamepads.connectedPlayers.rumble(500, HapticIntensity.Heavy);

// or a custom haptic pattern for all players
gamepads.connectedPlayers.haptic({
  duration: 200,
  strong: 0.8,
  weak: 0.4,
});

// 30ms trigger rumble haptic feedback for all connected players
gamepads.connectedPlayers.leftTrigger.rumble();
gamepads.connectedPlayers.rightTrigger.rumble();

// pause the game when any player presses the Select/Back button
if (gamepads.anyPlayer.justPressed(Button.Select)) {
  spud.pauseGame();
}

// don't worry about p1, p2, etc. for a single-player game.
// instead just use whichever connected controller is actually triggering inputs.
const isJumping = gamepads.singlePlayer.isDown(Button.South);

// loop through just the connected controllers
gamepads.connectedPlayers.list.forEach((player) => {
  const info = identifyGamepad(player.gamepad?.id ?? '');
  const gp = player.gamepad;
  console.log(
    `${spud.players[player.index].name}: ${info.productName} ${info.generalModel} controller "${gp?.id ?? ''}"`,
  );
});
```

There's also the `playerCount` property showing the number of currently connected controllers. It's effectively the same as `gamepads.connectedPlayers.list.length`.

```ts
console.log(`${gamepads.playerCount} controller(s) connected`);
```

#### `players` [#players]

Currently only includes the `.list` property, which returns a tuple containing four `Player` objects that have nullable `gamepad` properties.

`players.list` is the same exact thing as `connectedPlayers.list` but also includes disconnected players. Unlike `connectedPlayers.list` this will:

* always return an array of length 4
* possibly include `null` values for the `gamepad` property on each player in the array

#### `clearInputs` [#clearinputs]

`clearInputs()` saves the current state of all gamepads so `wasDown`, `justPressed`, `justReleased`, `buttonHeld`, `justConnected`, and `justDisconnected` work correctly on the next frame. Call this once per update, after your update logic and before the next update frame begins.

### Angle degrees [#angle-degrees]

the angle of the left analog stick in degrees in the range \[0, 360)

angles are clockwise: 0° is right/east, 90° is down/south, 180° is left/west, 270° is up/north.
note that positive y-axis values indicate "down" on analog sticks.

### Motion [#motion]

detect directional motion of the left analog stick with activation/deactivation thresholds.

this method uses "hysteresis" to prevent jittery state changes. this means movement is activated when the
stick's normalized magnitude exceeds the activation threshold (0.75 by default) and is deactivated when it returns below the deactivation threshold (0.25 by default).

the api is different for 4-way vs 8-way directions.

#### 4-way directions [#4-way-directions]

1. we give you both the x direction (Left | Right) and the y direction (Up | Down).
2. activation thresholds are based on the normalized axis values for x and y independently.

In other words, you can have `justMoved.Up` and `justMoved.Left` at the same time (unlike in the 8-way directional version).

#### 8-way directions [#8-way-directions]

1. we give you only a single cardinal direction (NE, S, SW, etc.).
2. the activation threshold is the magnitude of the normalized vector that combines both x and y.

#### Other info [#other-info]

* `motion.justMoved` values are only set for a single frame, so you don't need to deduplicate them.
* `motion.direction` values are persistent and only unset when the stick goes below the deactivation threshold.

### Resume cooldown [#resume-cooldown]

prevent input bleed-through on resume by artificially preventing A from being pressed
within the first second of being resumed unless it's been released within that timeframe.

## Controller layouts [#controller-layouts]

The browsers aren't much help when it comes to determining the controller model or layout, so we've been compiling our own list of gamepad layouts.

Here's the rundown: each `Gamepad` has an `id` property that will look something like `"810-3-USB Gamepad"` or `"Xbox Elite Series 2 Wireless Controller (STANDARD GAMEPAD Vendor: 045e Product: 0b22)"`, depending on your browser and gamepad model. These IDs contain both a vendor ID and a USB ID as well as (sometimes) the model name. With only the vendor IDs you can get close to knowing the base layout of the controller:

```ts
const microsoftVendorId = "045e"; // xbox layout
const sonyVendorId = "054c"; // playstation layout
const nintendoVendorId = "057e"; // nintendo layout
```

But there are still many other widely used gamepads with vendor IDs not included here, as well as generic vendor IDs like `810` that early PlayStation controllers used.

We've aggregated vendor and USB product IDs from...

* The Linux kernel
  * [http://www.linux-usb.org/usb.ids](http://www.linux-usb.org/usb.ids)
  * [https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L134](https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c#L134)
  * [https://github.com/torvalds/linux/blob/master/drivers/input/joydev.c#L751](https://github.com/torvalds/linux/blob/master/drivers/input/joydev.c#L751)
* Löve2D
  * [https://github.com/isitLoVe/ControllerTools/blob/bc73feaf4b6eb532682072b6e08ccbc61bd70609/backend/src/api/xbox.rs](https://github.com/isitLoVe/ControllerTools/blob/bc73feaf4b6eb532682072b6e08ccbc61bd70609/backend/src/api/xbox.rs)
* Unity
  * [https://raw.githubusercontent.com/winalex/Unity3d-InputMapper/master/Assets/StreamingAssets/profiles.txt](https://raw.githubusercontent.com/winalex/Unity3d-InputMapper/master/Assets/StreamingAssets/profiles.txt)
* Various GitHub gists and open source files
  * [https://github.com/elnormous/gamepads/tree/4b84620959d6d847b6264bd530e936d68e153142](https://github.com/elnormous/gamepads/tree/4b84620959d6d847b6264bd530e936d68e153142)
  * [https://github.com/mdqinc/SDL\_GameControllerDB/blob/master/gamecontrollerdb.txt](https://github.com/mdqinc/SDL_GameControllerDB/blob/master/gamecontrollerdb.txt)
  * [https://gist.githubusercontent.com/nondebug/aec93dff7f0f1969f4cc2291b24a3171/raw/25d6cfd6f41716f909b089558c9e4941b1726c41/known\_gamepads.txt](https://gist.githubusercontent.com/nondebug/aec93dff7f0f1969f4cc2291b24a3171/raw/25d6cfd6f41716f909b089558c9e4941b1726c41/known_gamepads.txt)
  * [https://raw.githubusercontent.com/360Controller/360Controller/master/360Controller/Info.plist](https://raw.githubusercontent.com/360Controller/360Controller/master/360Controller/Info.plist)

...and compiled and deduplicated these into a list you can access from the `identifyGamepad` function in the [Spud API](https://www.npmjs.com/package/@spud.gg/api).
