--- title: "Cursed components: a dvorak-only text input" date: 2024-04-19T11:00:00.000Z tags: - javascript - html - draft --- This post was inspired by Terence Eden's post about [inputs that don't work with numpads](https://shkspr.mobi/blog/2024/04/i-cant-use-my-number-pad-for-2fa-codes/), and one of the [subsequent comments](https://mastodon.social/@Edent/112286509959315116). This got me thinking, wouldn't it be absolutely beyond cursed to have a text input that only allowed you to enter text if you were using a sufficiently cool keyboard layout. So, I present, the Dvorak-only Text Input: **TODO:** Make the input ## Detecting keyboard layout There's no _actual_ way to detect the keyboard layout as a named value using Javascript, but there is the [KeyboardLayoutMap](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardLayoutMap), which is currently supported in Chromium browsers and not much else[^1]. So what we can do is lookup how the user's keyboard layout maps particular keys, and then compare those to what we'd expect from a Dvorak layout. ```javascript const dvorakKeys = [ ["KeyQ", "'"], ["KeyW", ","], ["KeyE", "."], ["KeyR", "p"], ["KeyT", "y"], ["KeyY", "f"] ]; navigator.keyboard.getLayoutMap().then(layoutMap => { const isUsingDvorak = dvorakKeys.every( ([keyCode, expected]) => layoutMap.get(keyCode) === expected ); if (isUsingDvorak) { // The user is (probably) using Dvorak } }) ``` Because this call happens inside a Promise, it can't be directly used within an `onChange` event on the text input, so instead it rechecks every 200ms and stores the result: ```javascript let isDvorak = false; const dvorakKeys = [ ["KeyQ", "'"], ["KeyW", ","], ["KeyE", "."], ["KeyR", "p"], ["KeyT", "y"], ["KeyY", "f"] ]; setInterval( () => navigator.keyboard.getLayoutMap().then(layoutMap => { isDvorak = dvorakKeys.every( ([keyCode, expected]) => layoutMap.get(keyCode) === expected ); }), 200 ); ``` ## Preventing the input This part's pretty simple. Basically, just [^1]: But that's pretty on-brand for this particular use-case.