The WebHID API allows websites to access alternative auxiliary keyboards and exotic gamepads.
Published: September 15, 2020
There are many human interface devices (HIDs), such as alternative keyboards or exotic gamepads, that are too new, too old, or too uncommon to be accessible by systems' device drivers. The WebHID API solves this by providing a way to implement device-specific logic in JavaScript.
Suggested use cases
An HID device takes input from or provides output to humans. Examples of devices include keyboards, pointing devices (mice, touchscreens, etc.), and gamepads. The HID protocol makes it possible to access these devices on desktop computers using operating system drivers. The web platform supports HID devices by relying on these drivers.
The inability to access uncommon HID devices is particularly painful when it comes to alternative auxiliary keyboards (such as Elgato Stream Deck, Jabra headsets, X-keys) and exotic gamepad support. Gamepads designed for desktop often use HID for gamepad inputs (buttons, joysticks, triggers) and outputs (LEDs, rumble).
Unfortunately, gamepad inputs and outputs are not well standardized and web browsers often require custom logic for specific devices. This is unsustainable and results in poor support for the long tail of older and uncommon devices. It also causes the browser to depend on quirks in the behavior of specific devices.
Terminology
A human interface device (HID) can take input or offer output to humans. There's an HID protocol, a standard for bi-directional communication between a host and a device that is designed to simplify the installation procedure.
HID consists of two fundamental concepts: reports and report descriptors. Reports are the data that is exchanged between a device and a software client. The report descriptor describes the format and meaning of data that the device supports.
Applications and HID devices exchange binary data through three report types:
| Report type | Description |
|---|---|
| Input report | Data that is sent from the device to the application (e.g. a button is pressed.) |
| Output report | Data that is sent from the application to the device (e.g. a request to turn on the keyboard backlight.) |
| Feature report | Data that may be sent in either direction. The format is device-specific. |
A report descriptor describes the binary format of reports supported by the device. Its structure is hierarchical and can group reports together as distinct collections within the top-level collection. The format of the descriptor is defined by the HID specification.
An HID usage is a numeric value referring to a standardized input or output. Usage values allow a device to describe the intended use of the device and the purpose of each field in its reports. For example, one is defined for the left button of a mouse. Usages are also organized into usage pages, which provide an indication of the high-level category of the device or report.
Use the WebHID API
To check if the WebHID API is supported, use:
if ("hid" in navigator) {
// The WebHID API is supported.
}
Open an HID connection
The WebHID API is asynchronous by design to prevent the website UI from blocking when awaiting input. This is important because HID data can be received at any time, requiring a way to listen to it.
To open an HID connection, first access a HIDDevice object. For this, you can
either prompt the user to select a device by calling
navigator.hid.requestDevice(), or pick one from navigator.hid.getDevices()
which returns a list of devices the website has been granted access to
previously.
The navigator.hid.requestDevice() function takes a mandatory object that
defines filters. Those are used to match any device connected with a USB vendor
identifier (vendorId), a USB product identifier (productId), a usage page
value (usagePage), and a usage value (usage). You can get those from the
USB ID Repository and the HID usage tables document.
The multiple HIDDevice objects returned by this function represent multiple
HID interfaces on the same physical device.
// Filter on devices with the Nintendo Switch Joy-Con USB Vendor/Product IDs.
const filters = [
{
vendorId: 0x057e, // Nintendo Co., Ltd
productId: 0x2006 // Joy-Con Left
},
{
vendorId: 0x057e, // Nintendo Co., Ltd
productId: 0x2007 // Joy-Con Right
}
];
// Prompt user to select a Joy-Con device.
const [device] = await navigator.hid.requestDevice({ filters });
// Get all devices the user has previously granted the website access to.
const devices = await navigator.hid.getDevices();
You can also use the optional exclusionFilters key in
navigator.hid.requestDevice() to exclude some devices from the browser picker
that are known to be malfunctioning.
// Request access to a device with vendor ID 0xABCD. The device must also have
// a collection with usage page Consumer (0x000C) and usage ID Consumer
// Control (0x0001). The device with product ID 0x1234 is malfunctioning.
const [device] = await navigator.hid.requestDevice({
filters: [{ vendorId: 0xabcd, usagePage: 0x000c, usage: 0x0001 }],
exclusionFilters: [{ vendorId: 0xabcd, productId: 0x1234 }],
});
A HIDDevice object contains USB vendor and product identifiers for device
identification. Its collections attribute is initialized with a hierarchical
description of the device's report formats.
for (let collection of device.collections) {
// An HID collection includes usage, usage page, reports, and subcollections.
console.log(`Usage: ${collection.usage}`);
console.log(`Usage page: ${collection.usagePage}`);
for (let inputReport of collection.inputReports) {
console.log(`Input report: ${inputReport.reportId}`);
// Loop through inputReport.items
}
for (let outputReport of collection.outputReports) {
console.log(`Output report: ${outputReport.reportId}`);
// Loop through outputReport.items
}
for (let featureReport of collection.featureReports) {
console.log(`Feature report: ${featureReport.reportId}`);
// Loop through featureReport.items
}
// Loop through subcollections with collection.children
}
The HIDDevice devices are by default returned in a "closed" state and must be
opened by calling open() before data can be sent or received.
// Wait for the HID connection to open before sending/receiving data.
await device.open();
Receive input reports

Once the HID connection is established, you can handle incoming input
reports by listening to the "inputreport" events from the device. Those events
contain the HID data as a DataView object (data), the HID device it belongs
to (device), and the 8-bit report ID associated with the input report
(reportId).
Continuing with the previous example, this code helps you detect which button the user has pressed on a Joy-Con Right device so that you can try it at home.
device.addEventListener("inputreport", event => {
const { data, device, reportId } = event;
// Handle only the Joy-Con Right device and a specific report ID.
if (device.productId !== 0x2007 && reportId !== 0x3f) return;
const value = data.getUint8(0);
if (value === 0) return;
const someButtons = { 1: "A", 2: "X", 4: "B", 8: "Y" };
console.log(`User pressed button ${someButtons[value]}.`);
});
Refer to the demo on CodePen.
Send output reports
To send an output report to an HID device, pass the 8-bit report ID associated
with the output report (reportId) and bytes as a BufferSource (data) to
device.sendReport(). The returned promise resolves once the report has been
sent. If the HID device does not use report IDs, set reportId to 0.
The next example applies to a Joy-Con device and shows you how to make it rumble with output reports.
// First, send a command to enable vibration.
// Magical bytes come from https://github.com/mzyy94/joycon-toolweb
const enableVibrationData = [1, 0, 1, 64, 64, 0, 1, 64, 64, 0x48, 0x01];
await device.sendReport(0x01, new Uint8Array(enableVibrationData));
// Then, send a command to make the Joy-Con device rumble.
// Actual bytes are available in the sample.
const rumbleData = [ /* ... */ ];
await device.sendReport(0x10, new Uint8Array(