Building a Function Key (F1–F12) Tester: Handling the Fn-Row Mess Across OSes
Function keys seem like the simplest possible thing to test — 12 keys, press them, see if they register. Building the Function Key Test for KeyboardTest.tech turned out to have more edge cases than expected, mostly becau
Function keys seem like the simplest possible thing to test — 12 keys, press them, see if they register. Building the Function Key Test for KeyboardTest.tech turned out to have more edge cases than expected, mostly because F-keys don't behave consistently across operating systems, browsers, or laptop firmware.
The base implementation is simple enough
javascript
const fKeys = ['F1','F2','F3','F4','F5','F6','F7','F8','F9','F10','F11','F12'];
const registered = new Set();
document.addEventListener('keydown', (e) => {
if (fKeys.includes(e.key)) {
registered.add(e.key);
updateKeyUI(e.key, true);
}
});
document.addEventListener('keyup', (e) => {
if (fKeys.includes(e.key)) {
updateKeyUI(e.key, false);
}
});
That covers the happy path. Here's where it got complicated.
Problem 1: laptops remap the entire row by default
On most laptops, F1–F12 aren't F-keys out of the box — they're brightness, volume, media playback, etc., with true function-key behavior gated behind an Fn modifier (or the reverse, depending on a BIOS/firmware toggle). A user can press what they believe is "F5" and actually send a browser refresh shortcut instead of a pure F5 keypress, or vice versa, depending on their Fn-lock state — and there's no reliable way to query that state from JavaScript. The most honest fix wasn't a code fix at all — it was adding a visible note in the UI: "If F-keys aren't registering, try holding Fn while pressing them (or check your Fn-lock setting)." Sometimes the right engineering answer is admitting what you can't detect and guiding the user instead.
Problem 2: browsers intercept some F-keys before your JS ever sees them
F1 (Help), F6 (address bar focus in some browsers), F11 (fullscreen), and F12 (DevTools) are commonly intercepted at the browser level for their own default behavior. Depending on the browser, your keydown listener may fire and the default action happens, or the default action wins and your listener never fires at all. This is inconsistent enough across Chrome/Firefox/Safari that I ended up documenting per-browser caveats directly in the tool's FAQ rather than pretending it's uniformly solvable — a decision that felt uncomfortable at first (admitting a limitation) but is more honest than silently failing.
Problem 3: e.key vs e.code actually matters here
Using e.key seemed natural since it directly gives you "F7" as a string. But e.key reflects the interpreted result, which can be affected by OS-level remapping. e.code ("F7" as a physical key code) is more reliable for "did this physical key get pressed" — which is the actual question a hardware tester needs answered, not "what action resulted from this press."
Problem 4: visually distinguishing "not tested yet" from "failed"
A key that simply hasn't been pressed yet looks identical to a key that was pressed but didn't register — from the UI's perspective, both are just "never got a keydown event." I ended up adding an explicit "press every key to complete the test" prompt and a completion indicator, so a user isn't left wondering whether an untested key silently failed.
Takeaway
The hardest part of this feature wasn't the JavaScript — it was accepting that a browser-based tester has a real ceiling on what it can verify when firmware/OS layers get involved, and building the UI to be honest about that ceiling instead of overclaiming certainty.
If you've built anything that tests hardware behavior through a browser sandbox, curious where you've hit similar detection ceilings — would like to compare notes.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.