Adding Keyboard Events to Vue

Vue js ux

Adding key actions can greatly improve the user experience of a webpage and is a must for games. It's a nice convenience to be able to navigate and perform other actions with keys. Utilizing arrow keys and tabs to move around the page also improves the quality of a webapp for people with disabilities. Accessibility is a much larger topic than I'm going to cover here though. I'll defer to this MDN article instead. While <input> elements can be tabbed to most others can not. Often times you may want to add tabbing or some other key support to a <div> or some other element.

Sections


Making an element focusable

Key modifiers in Vue

Key up and down events in a list

Making an element focusable


An item has to be focusable in order to attach key events to it. I'm not sure if this list is definitive but in general these elements are focusable:

  • HTMLInputElement
  • HTMLSelectElement
  • HTMLTextAreaElement
  • HTMLAnchorElement
  • HTMLButtonElement
  • HTMLAreaElement

If an element isn't focusable we can easily make it so by setting the tab index of an element to 0.

<div tabindex="0"></div>

That's all it requires. If you have a complex use case you may have to look at grouping and other fine controls. The focus of this article is handling key actions other than tab with Vue so please see Keyboard-navigable JavaScriptWidgets in the MDN docs for a deeper dive into using tabindex.

Key modifiers in Vue


Binding keyboard events in Vue is pretty straightforward. If you're not already familiar with event binding in Vue read over the event handling documents.

To bind a key action attach a modifier to the event. Here showMessage() is triggered when the escape key is pushed:

<div tabindex="0" @keydown.esc="showMessage()">Click me and press escape</div>

This will trigger the method whenever the key is pressed. You can try it out in the demos section.

While using event directives + modifier works well and is useful, especially for just one off key events or if the keys trigger different methods, the example below uses a more generalized approach. To see all event directives and modifiers take a look at the docs.

Something to note here is that I'm using the shorthand @ like above instead of using the v-on: directive for events like below:

<div tabindex="0" v-on:keydown.esc="showMessage">Click me and press escape</>

The Vue style guide recommends the directive shorthands as the preferred way. It looks nicer and reduces clutter when you start having more complex components. There's also shorthand syntax for binding values like this example in the style guide.

Key up and downs with a list


This example uses a simple list with a v-for to demonstrate using up and down arrow key events. Here's the basic Vue list component we'll use as a demonstration:

<!-- DemoList.vue -->
<template>
  <section class="food-list">
      <ul class="w4 list bt br bl">
        <li class="pointer hover bb tc pt1 pb1"
        tabindex="0"
        v-for="item in input" :key="item">{{item}}</li>
      </ul>
  </section>
</template>
 
<script>
export default {
  props: { input: { type: Array, required: true } }
}
</script>
 
<style scoped <style lang="scss" scoped>
.food-list{
  ul{
    list-style-type: none;
    overflow-wrap: break-word;
    -webkit-padding-start: 0px;
  }
}
</style>

You can find a demo of the component in the demos section and the complete code in this github repo.

In case you're wondering where the css classes not listed in the style section come from like: <ul class="w4 list bt br bl"> I'm using tachyons for utility classes. If you haven't used it before you should check it out. Other than that it's a pretty standard single page component.

Now we just have a list with some items in it. Lets capture some keyboard events. First we'll define the method that will handle the events in the methods section of the <script> block.

<script>
  export default {
    props: { input: { type: Array, required: true } },
    data: () => ({
      selection: 0,
    }),
    methods: {
      select(key, index) {
        if (key === "ArrowDown") {
          if (index === this.input.length - 1) {
            return;
          }
          let next = index + 1;
          document.getElementById("item_" + next).focus();
        }
        if (key === "ArrowUp") {
          if (index <= 0) {
            return;
          }
          let prev = index - 1;
          document.getElementById("item_" + prev).focus();
        }
      },
    },
  };
</script>

The method select (key, index) uses the items index to select the next or previous element in the list depending on the key and adds focus to the element. Next add the event directive to the <li> tag and a dynamic id:

<ul class="w4 list bt br bl">
  <li
    class="pointer hover bb tc pt1 pb1"
    tabindex="0"
    v-for="(item, index) in input"
    :key="item"
    :id="'item_' + index"
    @keydown.prevent="select($event.key, index)"
  >
    {{item}}
  </li>
</ul>

These two additions allow us to traverse the list using the up down arrows. We also added :id="'item_' + index" to the <li> tag. This will generate a dynamic id for the list items so that there is something to select when we call document.getElementById('item_' + next).focus() in the select method.

One annoyance is that if the page is scrolling the up/down arrows will scroll the page as well as change the focus of the list item. To improve user experience we should stop the scroll behaviour while using the list. To fix this we need to prevent default browser behavior. Vue provides a handy event modifier for this: .prevent. When attached to the @keydown directive it will prevent default behaviour. The final event handler will look like this: @keydown.prevent="select($event.key, index)". The usual mechanism for prevent default is to call Event.preventDefault() in an event handler method or something similar. It's generally best to use Vue's mechanisms for working with the DOM rather than the regular Web API although you could.

.prevent is useful in many situations particularly with <input> elements in forms where the browser wants to perform a submit using an url.

The final component should look like this:

<!-- DemoList.vue -->
 
<template>
  <section class="food-list">
      <h4 class="mt2">Japanese food:</h4>
      <ul class="w4 list bt br bl">
        <li class="pointer hover bb tc pt1 pb1" tabindex="0"
        v-for="(item, index) in input"
        :key="item"
        :id="'item_' + index"
        @keydown="select($event.key, index)">{{item}}</li>
      </ul>
  </section>
</template>
 
<script>
export default {
  props: { input: { type: Array, required: true } },
  data: () => ({
    selection: 0
  }),
  methods: {
    select (key, index) {
      if (key === 'ArrowDown') {
        if (index === this.input.length - 1) {
          return
        }
        let next = index + 1
        document.getElementById('item_' + next).focus()
      }
      if (key === 'ArrowUp') {
        if (index <= 0) {
          return
        }
        let prev = index - 1
        document.getElementById('item_' + prev).focus()
      }
    }
  }
}
</script>
 
<style scoped <style lang="scss" scoped>
.food-list {
  ul {
    list-style-type: none;
    overflow-wrap: break-word;
    -webkit-padding-start: 0px;
  }
}
</style>

This is just a simple example but a good illustration of how to utilize keys with Vue. You should be able to build more complex use cases from this one. If you just need to handle one or two different keys for different actions using the event directive + modifier is probably best. The list example in this post could have been written using the .up and .down modifiers and it would be a good exercise for you to refactor it to do so.

Vue provides these keyboard modifiers:

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

As of Vue 2.5 with automatic modifiers you can attach any key name as a modifier by converting it to kebab case. Here's an example from Vue's docs:

<input @keyup.page-down="onPageDown" />

You should be able attach events to any of the key values in this MDN web doc.

Vue provides a number of useful event handling mechanisms you should read through the docs and play around with them. This cheatsheet is a handy reference for them and the rest of Vue's API. There's a lot possible so it's worth a definitely worth a deeper look.

Links


Code
Demo components
Keyboard-navigable JavaScriptWidgets
Tachyons
Vue cheatsheet
Vue directive shorthand
Vue event handling