The WebHID API allows websites to access alternative auxiliary keyboards and exotic gamepads.


![François Beaufort](https://web.dev/images/authors/beaufortfrancois.jpg) François Beaufort [GitHub](https://github.com/beaufortfrancois)

<br />


Published: September 15, 2020

<br />


Browser Support

- ![Chrome: 89.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 89.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: not supported.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: not supported.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/HID)

<br />

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](https://www.usb.org/hid) 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](https://www.elgato.com/en/gaming/stream-deck), [Jabra
headsets](https://www.jabra.com/business/office-headsets), [X-keys](https://xkeys.com/xkeys.html)) 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](https://gist.github.com/beaufortfrancois/583424dfef66be1ade86231fd1a260c7) 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](http://www.linux-usb.org/usb-ids.html) and the [HID usage tables document](https://usb.org/document-library/hid-usage-tables-12).

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();

![](https://developer.chrome.com/static/docs/capabilities/hid/image/screenshot-an-hid-device-e26b194feb58a.jpg) User prompt for selecting a Nintendo Switch Joy-Con.

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

![Nintendo Switch Joy-Cons.](https://developer.chrome.com/static/docs/capabilities/hid/image/red-blue-nintendo-switch-9754d1c61bf3.jpg)

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`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/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](https://cdpn.io/web-dot-dev/fullpage/NPqZqox).

### 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`](https://developer.mozilla.org/docs/WebAssembly/JavaScript_interface/instantiate_static#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(rumbleData));

Refer to the [demo on CodePen](https://cdpn.io/web-dot-dev/fullpage/MYwMwMo).

### Send and receive feature reports

![](https://developer.chrome.com/static/docs/capabilities/hid/image/black-silver-laptop-comp-91a25010b2b48.jpg)

Feature reports are the only type of HID data reports that can travel in both
directions. They allow HID devices and applications to exchange non standardized
HID data. Unlike input and output reports, feature reports are not received or
sent by the application on a regular basis.

To send a feature report to an HID device, pass the 8-bit report ID associated
with the feature report (`reportId`) and bytes as a [`BufferSource`](https://developer.mozilla.org/docs/WebAssembly/JavaScript_interface/instantiate_static#buffersource) (`data`) to
`device.sendFeatureReport()`. The returned promise resolves once the report has
been sent. If the HID device does not use report IDs, set `reportId` to 0.

This example illustrates the use of feature reports by showing you how to
request an Apple keyboard backlight device, open it, and make it blink.

    const waitFor = duration => new Promise(r => setTimeout(r, duration));

    // Prompt user to select an Apple Keyboard Backlight device.
    const [device] = await navigator.hid.requestDevice({
      filters: [{ vendorId: 0x05ac, usage: 0x0f, usagePage: 0xff00 }]
    });

    // Wait for the HID connection to open.
    await device.open();

    // Blink!
    const reportId = 1;
    for (let i = 0; i < 10; i++) {
      // Turn off
      await device.sendFeatureReport(reportId, Uint32Array.from([0, 0]));
      await waitFor(100);
      // Turn on
      await device.sendFeatureReport(reportId, Uint32Array.from([512, 0]));
      await waitFor(100);
    }

Refer to the [demo on CodePen](https://cdpn.io/web-dot-dev/fullpage/ogXrXrJ).

To receive a feature report from an HID device, pass the 8-bit report ID
associated with the feature report (`reportId`) to
`device.receiveFeatureReport()`. The returned promise resolves with a
[`DataView`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/DataView) object that contains the contents of the feature report. If the HID
device does not use report IDs, set `reportId` to 0.

    // Request feature report.
    const dataView = await device.receiveFeatureReport(/* reportId= */ 1);

    // Read feature report contents with dataView.getInt8(), getUint8(), etc...

### Listen to connection and disconnection

When the website has been granted permission to access an HID device, it can
actively receive connection and disconnection events by listening to `"connect"`
and `"disconnect"` events.

    navigator.hid.addEventListener("connect", event => {
      // Automatically open event.device or warn user a device is available.
    });

    navigator.hid.addEventListener("disconnect", event => {
      // Remove |event.device| from the UI.
    });

### Revoke access to an HID device

The website can clean up permissions to access an HID device it is no longer
interested in retaining by calling `forget()` on the `HIDDevice` instance. For
example, for an educational web application used on a shared computer with many
devices, a large number of accumulated user-generated permissions creates a poor
user experience.

Calling `forget()` on a single `HIDDevice` instance will revoke access to all
the HID interfaces on the same physical device.

    // Voluntarily revoke access to this HID device.
    await device.forget();

As `forget()` is available in Chrome 100 or later, check if this feature is
supported with the following:

    if ("hid" in navigator && "forget" in HIDDevice.prototype) {
      // forget() is supported.
    }

## Dev tips

![The internal page to debug HID.](https://developer.chrome.com/static/docs/capabilities/hid/image/screenshot-the-internal-958992d62d73e.jpg)

Debug HID in Chrome with the internal page, `about://device-log`
where you can see all HID and USB device related events in one single place.

Check out the [HID explorer](https://nondebug.github.io/webhid-explorer/) for dumping HID device
info into a human-readable format. It maps from usage values to names for each
HID usage.

On most Linux systems, HID devices are mapped with read-only permissions by
default. To allow Chrome to open an HID device, you will need to add a new [udev
rule](https://www.freedesktop.org/software/systemd/man/udev.html). Create a file at `/etc/udev/rules.d/50-yourdevicename.rules` with the
following content:

    KERNEL=="hidraw*", ATTRS{idVendor}=="[yourdevicevendor]", MODE="0664", GROUP="plugdev"

In this code, `[yourdevicevendor]` is `057e`, for instance if your device is a
Nintendo Switch Joy-Con. `ATTRS{idProduct}` can be added for a more specific
rule. Make sure your `user` is a [member](https://wiki.debian.org/SystemGroups) of the `plugdev` group. Then, just
reconnect your device.

## Demos

Some WebHID demos are listed at [web.dev/hid-examples](https://developer.chrome.com/docs/capabilities/web-apis/hid-examples).

## Security and privacy

The spec authors have designed and implemented the WebHID API using the core
principles defined in [Controlling Access to Powerful Web Platform Features](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/security/permissions-for-powerful-web-platform-features.md),
including user control, transparency, and ergonomics. The ability to use this
API is primarily gated by a permission model that grants access to only a single
HID device at a time. In response to a user prompt, the user must take active
steps to select a particular HID device.

To understand the security tradeoffs, check out the [Security and Privacy
Considerations](https://wicg.github.io/webhid/#security-and-privacy) section of the WebHID spec.

On top of this, Chrome inspects the usage of each top-level collection and if a
top-level collection has a protected usage (e.g. generic keyboard, mouse), then
a website won't be able to send and receive any reports defined in that
collection. The full list of protected usages is [publicly available](https://source.chromium.org/chromium/chromium/src/+/main:services/device/public/cpp/hid/hid_report_utils.cc).

Note that security-sensitive HID devices (such as FIDO HID devices used for
stronger authentication) are also blocked in Chrome. See the [USB blocklist](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/usb/usb_blocklist.cc) and
[HID blocklist](https://source.chromium.org/chromium/chromium/src/+/main:services/device/public/cpp/hid/hid_blocklist.cc) files.

## Feedback

The Chrome team would love to hear about your thoughts and experiences with the
WebHID API.

### Tell us about the API design

Is there something about the API that doesn't work as expected? Or are there
missing methods or properties that you need to implement your idea?

File a spec issue on the [WebHID API GitHub repo](https://github.com/wicg/webhid/issues) or add your thoughts
to an existing issue.

### Report a problem with the implementation

Did you find a bug with Chrome's implementation? Or is the implementation
different from the spec?

Check out [How to file WebHID bugs](https://www.chromium.org/developers/how-tos/file-web-hid-bugs/). Be sure to include as much
detail as you can, provide instructions to reproduce the bug, and set
*Components* to `Blink>HID`.

## Helpful links

- [Specification](https://wicg.github.io/webhid/)
- [Tracking bug](https://crbug.com/890096)
- [ChromeStatus.com entry](https://chromestatus.com/feature/5172464636133376)
- Blink Component: [`Blink>HID`](https://chromestatus.com/features#component%3ABlink%3EHID)

## Acknowledgements

Thanks to [Matt Reynolds](https://github.com/nondebug) and [Joe Medley](https://github.com/jpmedley) for their reviews.