I'm working on keyboard testing web application. As a mechanical keyboard enthusiast, I need to test that all the keys on my keyboard work whenever I build one, or change the firmware for one. Eventually, I'd also like to implement some typing tests that are similar to MonkeyType. This isn't a particularly new problem that's unsolved, but I thought it was a good chance to learn some Vue and TypeScript.

So far the codebase is only 3 files (that I've added on top of whatever Vue generates), but I have some questions. Full codebase is on GitHub if you'd like to run it

I'm brand new to Vue and TypeScript, so the things I'd like to know are:

  • Am I doing things idiomatically?
  • Am I sending events from Keyboard to Key in a way that makes sense?
    • This is my biggest question. Coming from other OOP languages, it made sense to make Key it's own component, but I feel like in Vue, there are going to be communication difficulties between Keyboard and Key. It's hard to decide where the state information (such as key position, not implemented yet) should go.
  • Is there a better way to structure my components, given that I may also need to add more attributes to Key (things such as location on a keyboard grid, size of the button, etc)?
  • When should I be using setup() vs data() or created() or mounted() etc.
    • It's quite confusing all the different things I've read only for using const state = reactive({...}) and ...toRefs(state) vs individual refs vs just using data() etc.
  • If I'm using TypeScript, should I still be using vanilla Javascript functions like Object.fromEntries()?

The 3 files of interest are the following:


  <h1>{{ state.appName }}</h1>
  <Keyboard />

<script lang="ts">
import { defineComponent, reactive } from 'vue';
import Keyboard from "@/components/Keyboard.vue";

export default defineComponent({
  name: 'App',
  components: { Keyboard },
  setup() {
    const state = reactive({
      appName: "Type Test"

    return { state }

  /* Used by Equal */
  @import url("https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined");
  @import url("https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;900&display=swap");


  <div v-on:keyup="keyPressed" class="keyboard" tabindex="0">
    <!-- tabindex is set to make the div focus-able, which is required to respond to keypress events -->
    <Key v-for="(data, name) in state.keys" :key="name" :name="name" :pressed="data.pressed"/>
  <it-button @click="resetKeys" type="danger">Reset</it-button>

<script lang="ts">
import { defineComponent, reactive } from 'vue';
import Key from '@/components/Key.vue';

export default defineComponent({
  name: "Keyboard",
  components: { Key },
  setup() {
    const state = reactive({
      keys: Object.fromEntries(
        ].map(name => [name, { pressed: false }])

    return { state }
  methods: {
    keyPressed(event: KeyboardEvent) {
      console.log("Pressed: " +  event.key);
      console.log("Code: " +  event.key);
      console.log("Location: " + event.location);
      if (this.state.keys[event.key]) {
        this.state.keys[event.key]['pressed'] = true;
    resetKeys() {
      let keys = Object.keys(this.state.keys)
      for (let key of keys) {
        this.state.keys[key]['pressed'] = false;


  <it-button class="key" :type="pressed ? 'primary' : ''">
    {{ name }}

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: "Key",
  props: {
    name: {
      type: String,
      required: true
    pressed: {
      type: Boolean,
      default: false

<style scoped>
.key {
  display: inline-block;
  margin: 1px;

.key.pressed {
  background-color: aquamarine;

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.