Bringing `KeyboardEvent.key` and `KeyboardEvent.keyCode` Altogether for the Best Keyboard Interaction Experience
Taufik Nurrohman
Posted on December 5, 2019
Photo by Sathesh D from Pexels
In ancient times, we were depending on the KeyboardEvent.keyCode
property for so long to detect which key we were pressing on the keyboard:
node.addEventListener('keydown', e => {
if (13 === e.keyCode) {
// Do something with `Enter` key
}
}, false);
~~~{% endraw %}
Years have passed and the diversity of today’s [keyboard layout](https://en.wikipedia.org/wiki/Keyboard_layout) makes me even more horrified. Most of us still using that old method to support the _en-US_ keyboard standard only (either consciously or not), which then gives us the possibility of unexpected results on other keyboard layouts. For example, on the Russian keyboard layout, the {% raw %}`,` key stands together with the `Б` key, so that when we have a custom keyboard interaction that requires the detection of comma character to perform certain actions, then usually that action will also be triggered when we actually want to type `Б`. We literally has ignored other keyboard layouts either because of lack of knowledge or because we feel that it just too much to be able to support all of them.
<figure>
<img alt="Russian Keyboard" src="https://user-images.githubusercontent.com/1669261/70196397-29cc8500-173b-11ea-9091-fafcc444dbb4.png">
<figcaption>Russian Keyboard</figcaption>
</figure>
Long story short, the {% raw %}`KeyboardEvent.keyCode`{% endraw %} is now deprecated. We now have a better alternative: {% raw %}`KeyboardEvent.key`{% endraw %} that displays whatever characters we type on an input without caring about the type of keyboard layout that we are currently using. It also works on non-printable characters such as that <kbd>Enter</kbd> and <kbd>Backspace</kbd> key which will produce {% raw %}`'Enter'`{% endraw %} and {% raw %}`'Backspace'`{% endraw %} string accordingly.{% raw %}
~~~ js
node.addEventListener('keydown', e => {
if (',' === e.key) {
// Do something with `,` key
}
}, false);
~~~{% endraw %}
It’s so cool that I want to pee here. Unfortunately, this feature is still not widely supported, especially on mobile devices. So it’s better to use both of them to get the best results:{% raw %}
~~~ js
node.addEventListener('keydown', e => {
let key = e.key,
keyCode = e.keyCode;
if (key && ',' === key || keyCode && 188 === keyCode) {
// Do something with `,` key
}
}, false);
~~~{% endraw %}
Since {% raw %}`KeyboardEvent.keyCode`{% endraw %} value mostly in line with character codes from the ASCII table, some people also like to use this method. Although this method will not work on non-printable characters, at least we try to give the best results with {% raw %}`KeyboardEvent.key`{% endraw %} as priority:{% raw %}
~~~ js
node.addEventListener('keydown', e => {
let key = e.key || String.fromCharCode(e.keyCode);
if (',' === key) {
// Do something with `,` key
}
}, false);
~~~{% endraw %}
For devices that support {% raw %}`KeyboardEvent.key`{% endraw %}, a key that cannot be identified will return {% raw %}`'Unidentified'`{% endraw %}. This case becomes very strange when I try to check it on my mobile device as it always produces {% raw %}`'Unidentified'`{% endraw %} on any key:
<figure>
<img alt="Google Chrome on Android Device" src="https://user-images.githubusercontent.com/1669261/70196398-2a651b80-173b-11ea-8f3f-dfa115e9b27b.jpg" width="300" height="513">
<figcaption>Chrome 78.0.3904.108</figcaption>
</figure>
Normally, when an object property does not exists (no {% raw %}`key`{% endraw %} property means no support for {% raw %}`KeyboardEvent.key`{% endraw %}), it should return {% raw %}`undefined`{% endraw %}. But it **wasn’t**. This leads me to conclude that {% raw %}`KeyboardEvent.key`{% endraw %} feature might already exist on my device, it’s just that it’s not working properly.
Maybe, the last way that looks pretty hacky but just works in an urgent situation is to check the last character that we entered. But since the last character don’t exist (yet) as the {% raw %}`keydown`{% endraw %} event is being performed, we need to delay the action in a fraction of a millisecond before retrieving the incoming characters:{% raw %}
~~~ js
node.addEventListener('keydown', e => {
// First try
if ( /* … */ ) {
// Do something with `,` key
} else {
setTimeout(() => {
// Second try
if (',' === node.value.slice(-1)) {
// Do something with `,` key
}
}, 1);
}
}, false);
~~~{% endraw %}
You can also use the [Text Selection Range API](https://github.com/taufik-nurrohman/text-editor) to handle this if you want to check the last character **exactly before the caret**, but it’s just too much, especially for HTML elements with `contenteditable` attribute (they simply have different API). And if you decide to use it, then it might be more beneficial for you to detect those characters through the incoming values and so forget about the `KeyboardEvent.key` feature detection.
By the way, the following is a project that I have made using the above concept. It beautifies your text input into a “tags” input, sort of. It also has better keyboard interaction support such as removing tags using <kbd>Delete</kbd> and <kbd>Backspace</kbd> keys. You can also navigate to other tags using the arrow keys:
{% github taufik-nurrohman/tag-picker %}
💖 💪 🙅 🚩
Taufik Nurrohman
Posted on December 5, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.