Working with the Keyboard in your Vue App

Working with the Keyboard in your Vue App

This weekend I started working on another game in Vue.js (if you're curious, you can take a peak at it here if you want). For part of the game I wanted to really make use of the keyboard for interaction. My goal, and I won't make it 100%, is a game where you can use the keyboard for the entire time you play. I knew that JavaScript had access to keyboard events, but I had never tried using them in Vue. Before I share what I found, I want to give a shoutout to LinusBorg of the Vue forums. The good stuff below is all him, the bad stuff and mistakes are my fault.

Alright, so let's start with a simple example. If you look at the Vue docs for event handling, you'll find a specific section that talks about key modifiers. This section discusses how you can add shortcuts to listen for specific keys. While not exactly what I was looking for, it reassured me that working with the keyboard was going to be easy. So for example, this will fire an event on every keyup call:

<input @keyup="keyEvent">

This modification will only fire when the enter key is pressed:

<input @keyup.enter="keyEvent">

Cool! But notice how the event is bound to an input field. For my needs, I wanted keyboard handling at the "app" level, by that I mean without having to use an input field first. Consider this example.

<div id="app" v-cloak @keyup.enter="test('div enter', $event)" @keyup="test('div',$event)">
  <input @keyup="test('input', $event)"><br/>
  enter only: <input @keyup.enter="test('second input', $event)">

I've got multiple uses of keyup here. I'm passing a label to my test handler as well as the $event object. I listen, twice, at the div level, and then once for each input field. My handler just echoes out what was passed in:

test(where, e) {
	console.log(`keyuptest at ${where} with code ${e.keyCode}`);

The result is interesting. If you type outside of any input field, nothing is registered. But if you first click into one of the two input fields, things work as expected. Both the input handler and div handler will fire. You can test this yourself at my Codepen.

So a bit more Googling, and I came across this Vue.js forum post: Capture keypress for all keys. In it, the poster asks about responding to any and all keypress events globally across the app. LinusBorg came up with a simple solution that boils down to this:

mounted() {
	window.addEventListener("keypress", e => {

In my testing, this worked great, but I ran into an interesting issue. My game makes use of routing and I only need to listen for keyboard events in one route. When I'd leave that route and return, the event listener would get bound again. The more I did this, the more duplicate event handlers were being bound for keypress.

I struggled with this some more, and again, LinusBorg came up with a solution. I knew about window.removeEventListener, but it doesn't work with anonymous functions. The solution was to just use a Vue method for both registering and removing the event. That may not make sense, but here's a simple example:

created() {
	window.addEventListener('keypress', this.doCommand);
destroyed() {
	window.removeEventListener('keypress', this.doCommand);
methods: {
	doCommand(e) {
		let cmd = String.fromCharCode(e.keyCode).toLowerCase();
		// do stuff

And that's it! Of course, things are a bit more complex in my game, but I'll leave those bits for the post describing my game. As always, I hope this helps!

Header photo by Csabi Elter on Unsplash

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA

Archived Comments

Comment 1 by dalibor posted on 8/12/2019 at 4:33 PM

Set tabindex="0" on #app for Key Modifiers up, down, space etc.

Comment 2 (In reply to #1) by dalibor posted on 8/12/2019 at 4:34 PM
Comment 3 (In reply to #2) by Raymond Camden posted on 8/12/2019 at 5:52 PM

Well that was easy. I tried @keydown="test" to catch it all and it worked. My only concern is that I had to click once on the document - which in theory may be an issue in my game. You definitely click on the first screen, but maybe not the second.

Comment 4 by Damian Dulisz posted on 8/12/2019 at 10:40 PM

Hey! For event listeners that are supposed to be global, you might want to check out this tiny library
It takes the output from the template compiler that includes all the modifiers like `.keyup.enter` and automatically assigns those to the window or document object.

Comment 5 (In reply to #3) by dalibor posted on 8/13/2019 at 7:50 AM

You can set `focus` on element which will receive keyboard and similar events by default.

Comment 6 (In reply to #4) by Raymond Camden posted on 8/13/2019 at 2:55 PM

That is damn neat!

Comment 7 (In reply to #5) by Raymond Camden posted on 8/13/2019 at 2:55 PM

Right, but if you have no form elements, would you call focus on the div as a whole in created() or some such?

Comment 8 by Andy posted on 8/29/2020 at 7:52 PM

Thank you for sharing how we can work with the keyboard in the Vue application. This is one of the best things that I've seen from you and if you continue to share more content like this, it would be great for me.

Comment 9 by yachris posted on 11/10/2020 at 4:24 PM

THANKS! This is so much help. For whatever it's worth, "keypress" does not detect the "Escape" key, but the "keydown" event does.