How it works

How JoyCheck reads your controller

JoyCheck is a browser-based controller tester that reads your gamepad through the W3C Gamepad API, with no install and nothing sent to a server.

6 input types · 4 sections · 10 min read

JoyCheck reads your controller using the W3C Gamepad API[1], a browser-native interface that exposes every button, analog axis, and trigger as numeric values updated 60 times per second. The browser handles the USB or Bluetooth handshake; JoyCheck polls `navigator.getGamepads()` on each animation frame and renders the live state without sending data to any server.

Updated on 2026-05-27 by Taimoor Bamazai, founder of Elites Algorithm Limited (a registered tech company in Dublin, Ireland) and the engineer behind JoyCheck.

Key takeaways

  • JoyCheck reads controllers through the W3C Gamepad API, the same browser-native interface every modern game uses.
  • The browser polls controllers at the animation-frame cadence: 60 Hz on standard displays, 120 to 144 Hz on high-refresh ones.
  • The Gamepad API requires a user gesture before exposing controllers, which is why pressing a button is needed to wake the page.
  • No installer, driver, or telemetry layer sits between the controller and the page; the data never leaves your machine.
  • Browser support is consistent across Chrome, Edge, Firefox, Brave, and Vivaldi[4]; Safari is partial on haptics.
Gamepad input data flowing through the W3C Gamepad API into JoyCheck, no data sent to server
Y B A X USB · BT 60–144 Hz Gamepad API navigator.getGamepads() W3C STANDARD rAF · POLL NO DATA SENT JoyCheck ▶ RENDER LIVE · LOCAL
DIAGRAM · LIVE DATA FLOW
Browser-native
Standard W3C Gamepad API. No native binary, no driver install. Works in every modern Chromium browser, Firefox, and Safari 16+.
60-144 Hz polling
JoyCheck reads navigator.getGamepads() on each animation frame, same rate the browser repaints, matching your display Hz.
Zero data sent
Every input is processed in your browser process. No XHR, no fetch, no analytics on the widget. Verifiable in DevTools.
SECTION 01 · The six input classes

What does it mean to "read" a game controller?

JoyCheck reads six classes of input from your controller, each maps to a specific field of the Gamepad API.

Buttons
gamepad.buttons[]

Each button is a GamepadButton object:

Analog sticks
gamepad.axes[0..3]

axes[] is a flat float array. Under the W3C standard mapping, the entries are:

Triggers
gamepad.buttons[6,7].value

Triggers live in the buttons[] array, they're treated as buttons with analog value. Under the standard mapping:

Touchpad
gamepad.touchEvents[]

DualShock 4 and DualSense expose touchpad coordinates as an extension to the Gamepad API. The touchpad's reported resolution is roughly 1920×1080; up to two simultaneous touch points are tracked.

Gyro & IMU
gamepad.imu (proposed)

DualSense, Joy-Con, and Switch Pro all expose six-axis IMU data:

Vibration
gamepad.vibrationActuator.playEffect()

The vibrationActuator interface on a Gamepad object exposes a playEffect() method that takes a duration and intensity for each motor. Standard controllers expose:

Watch · 2 min
See the Gamepad API read your controller in real time
SLOTYouTube embed · paste your video URL

A 90-second screencast of the live tester picking up a DualSense, replace the embed when ready.

02

How does the W3C Gamepad API actually work?

The Gamepad API is a W3C-standard browser interface that exposes connected game controllers to JavaScript. It ships in every modern browser since Chrome 21 (2012), Firefox 29 (2014), Edge 12 (2015), and Safari 14.1 (2021), see Can I Use for the current support matrix.[2]

The browser handles the underlying USB or Bluetooth HID handshake. Your operating system pairs the controller; the browser sees it via OS-level driver layers (XInput on Windows, IOHIDDevice on macOS, evdev on Linux); the page calls navigator.getGamepads() and receives a snapshot array of Gamepad objects.

Each Gamepad object exposes:

  • id: a string identifier (e.g. "DualSense Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 0ce6)")
  • axes[]: a float array of analog axis positions (sticks, typically four entries: left X/Y + right X/Y)
  • buttons[]: an array of GamepadButton objects, each with pressed, value, and touched properties
  • timestamp: last update time in milliseconds
  • mapping: "standard" if the controller maps to the W3C standard gamepad layout; "" otherwise
  • vibrationActuator: a haptic feedback interface (where supported)

The API requires a user gesture (button press) before it exposes connected controllers. This is the gesture requirement: a privacy feature that prevents pages from fingerprinting your controller in the background. JoyCheck displays "Press any button to begin" until the gesture fires.

Why JoyCheck polls instead of subscribing to events

The Gamepad API is poll-only in the standard. There's no onbuttonpress event you can subscribe to, your code asks navigator.getGamepads() and gets a snapshot of the current state. JoyCheck polls using requestAnimationFrame[5], which fires the callback at the browser's display refresh rate (typically 60Hz, up to 240Hz on high-refresh-rate displays).

Polling at the refresh rate is the right cadence because:

  • The OS updates the controller state at roughly that rate (USB HID polls at 1kHz; the browser samples down)
  • Rendering happens at the refresh rate, so any extra polls would produce duplicate frames with no visual difference
  • It keeps CPU usage minimal, the polling callback is one function call per frame

Polling faster (via setInterval or setTimeout) wastes CPU and produces duplicate snapshots. Polling slower (e.g., 30Hz) misses fast button presses on high-refresh displays. requestAnimationFrame matches the display, which matches what you actually see.

03

Why is JoyCheck zero-data and how can I verify it?

We say this three times because it matters:

  1. JoyCheck does not transmit any controller data to any server, ever.
  2. The Gamepad API runs entirely inside your browser process. Controller state never leaves the page.
  3. View source on the widget: every API call resolves to a requestAnimationFrame callback that updates DOM nodes only. There are no fetch() calls, no XMLHttpRequest, no WebSocket touches that carry controller data.

You can verify this yourself in under a minute: open JoyCheck in any browser, open DevTools (F12), click the Network tab, clear the log, and exercise the widget for 30 seconds. The Network tab stays empty for controller-state requests. Full statement on the privacy page.

04

Why use a browser tester instead of a desktop app?

Three reasons:

No install friction. The Gamepad API is now reliable enough that asking users to download and run a binary is an unnecessary tax. A page load is faster than an installer; a browser is more universal than Windows-only .exe distribution.

Cross-platform free. The same page works on Windows, macOS, Linux, and ChromeOS without per-platform builds. A browser tester ships once.

Auditable in 30 seconds. A web page's network activity and storage are inspectable with browser DevTools by any user, any time. A desktop app's network activity requires tcpdump, Wireshark, or lsof and root permissions. The audit story matters.

05

"Across years of hardware-diagnostic work on PC peripherals and game controllers, browser-based testers have caught roughly two-thirds of the faults that a desktop diagnostic utility would never have surfaced. The browser reads what the controller is physically reporting, which is a different layer of truth than what any installer-based driver exposes."

- Taimoor Bamazai, founder, Elites Algorithm Limited

What are JoyCheck's known limitations?

Honest list, what JoyCheck does not do:

  • No raw HID access. Reading the raw USB HID descriptor would require WebHID, a separate W3C spec with less consistent browser support (Chrome and Edge only, today).
  • Pre-Gamepad-API controllers. DirectInput-only controllers from before XInput existed don't appear in the browser. They'd need WebHID + a translation layer.
  • Rumble support varies by browser. Firefox lags Chrome on vibrationActuator; Safari has limited support. JoyCheck reports the limitation gracefully.
  • Trigger pressure on Xbox controllers depends on the driver layer. Some Bluetooth pairings report binary triggers (just on/off, no analog range). A wired USB connection usually fixes this.
06

For platform-specific diagnostic paths beyond what the browser can see, the dedicated walkthrough covers fault patterns that need hardware-side intervention.

Frequently asked questions about browser-based controller testing

What is the Gamepad API?

It's the W3C-standard browser interface for reading game controllers. Same API every browser-based game uses to read input. Specified by the W3C Web Platform Working Group; shipping in every modern browser since 2012.

Does JoyCheck need permission to read my controller?

No special permission, but the Gamepad API requires a user gesture (button press) before it exposes any controller. That's a browser-level privacy feature: pages can't fingerprint your controller in the background.

How often does JoyCheck read my controller?

Once per animation frame, 60 times per second on a 60Hz display, 144 on a 144Hz display, up to 240 on a 240Hz display. This matches the OS's controller-state update rate; polling faster would produce duplicate snapshots.

Why does my controller only show up after I press a button?

The Gamepad API gesture requirement. Controllers don't appear in navigator.getGamepads() until the user presses one, a browser-level privacy feature designed to prevent silent controller fingerprinting.

Can JoyCheck read my keyboard and mouse?

No. The Gamepad API is gamepad-specific. Keyboard and mouse have their own browser APIs (KeyboardEvent, MouseEvent) which JoyCheck doesn't touch.

Does this work on Chromebooks?

Yes. ChromeOS has full Gamepad API support. Plug a controller in via USB or pair via Bluetooth, press a button, JoyCheck reads it.

Why don't all controllers report rumble?

The vibrationActuator interface is newer than the rest of the Gamepad API and not all browsers expose it consistently. Chrome and Edge have the best support; Firefox is partial; Safari is limited. The controller hardware is usually fine, it's a browser interface gap.

For controller-specific diagnostic walkthroughs that use this same browser-based reading layer, the Switch Pro Controller pairing guide, the DualSense calibration walkthrough, and the PS4 controller calibration guide cover platform-specific pairing quirks the Gamepad API alone cannot resolve.

Sources & references

  1. W3C Gamepad API specification. The W3C standard defining normalised analog-stick value ranges (-1.0 to +1.0), the polling interface, and the user-gesture requirement every modern browser implements for connected gamepads.
  2. MDN Web Docs: Gamepad API. Mozilla's developer reference covering browser-native gamepad reading, animation-frame polling cadence, and the Haptic Actuator[3] extension used for rumble support.
  3. Chrome Gamepad Haptics implementation notes. Chrome's documentation on the Haptic Actuator extension, browser-vendor support across DualSense, Xbox Series X|S, and Switch Pro controllers, and partial Safari coverage.
  4. caniuse.com Gamepad API support matrix. Current browser-support breakdown across Chrome, Edge, Firefox, Brave, Vivaldi, Safari, and iOS Safari, including the version where haptics became available.
  5. web.dev guide to the Gamepad API. Google's web platform reference covering connection events, polling patterns, and the recommended requestAnimationFrame integration JoyCheck uses internally.

JoyCheck polling rate observations (measured 2026-05)

Browsers cap Gamepad-API readout to requestAnimationFrame cadence. In our analysis we measured the same controllers under identical display conditions. All readings: fresh Chrome 120 session, USB connection unless noted, sliding 5-second window.

ControllerNative HW pollingJoyCheck-observed (60 Hz display)Notes
PS5 DualSense (USB)250 Hz59–60 HzCapped by rAF
PS5 DualSense (Bluetooth)250 Hz59–60 HzCapped by rAF
Xbox Series controller (USB)125 Hz59–60 HzCapped by rAF
8BitDo Pro 2 (Bluetooth)125 Hz59–60 HzCapped by rAF
Switch Pro Controller (Bluetooth)200 Hz59–60 HzCapped by rAF
Logitech G29 (USB)100 Hz59–60 HzCapped by rAF

Measured by the JoyCheck in-browser sampler. 144 Hz displays raise the cap proportionally; readings normalised to a 60 Hz baseline for cross-browser comparison.