How to Style an Element Temporarily using Vue

✔︎ Last updated on April 7th, 2024

Applying certain styles temporarily is a good way to give your user a visual feedback. In this article, I will explain how you can apply the flash style on an element to let the user know that a certain key was pressed or a certain element was detected.


The other day, I was building a typing-based app in Vue. Part of the app was an on-screen keyboard containing all the keys for the English alphabet. My goal was to make the onscreen keys “flash” when the user presses them on the keyboard. The keys after flashing briefly will quickly go back to normal.

The first thought in my mind was to create a data property called isFlashed and bind a class to it dynamically, like this —

HTML
<span :class="['key', {flash: isFlashed}]"> 
    {{ Keyname }}
</span>

However, I quickly realized that to make this work for all 26 keys, I’d need to track the isFlashed state for each key independently. This meant turning the keys into their own components. Since my app was simple, I didn’t want to overcomplicate things by creating a bunch of components just for this feature.

Must read: How to Lighten or Darken a Color with Pure CSS

Thankfully, I realized I didn’t need to convert them to components. JavaScript itself has a built-in tool called setTimeout which is perfect for my needs. It lets you run a bit of code after a set amount of time, which was perfect for my flashing key effect!

Set up

This is the code we are starting with:

HTML

HTML
<div id="app">
	<div class="keyboard-container">
		<div class="keyboard-row">
			<span v-for="key in row1" :key="key" class="key">{{ key }}</span>
		</div>
		<div class="keyboard-row">
			<span v-for="key in row2" :key="key"  class="key">{{ key }}</span>
		</div>
		<div class="keyboard-row">
			<span v-for="key in row3" :key="key"  class="key">{{ key }}</span>
		</div>
	</div>
</div>

CSS

CSS
.keyboard-container {
	padding: 80px;
	display: grid;
	gap: 5px;
}

.keyboard-row {
	display: flex;
	justify-content: center;
}

.key {
	padding: 12px 15px;
	border: 1px solid #ccc;
	border-radius: 5px;
	user-select: none; /* Prevent text selection */
	margin: 2px;
	background: whitesmoke;
}

JavaScript

JavaScript
app = Vue.createApp({
	data() {
		return {
			row1: ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
			row2: ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
			row3: ["Z", "X", "C", "V", "B", "N", "M"],
		};
	},
});

app.mount("#app");

We are storing the key names in three arrays, one for each row of the keyboard. These keys are then displayed on screen using the v-for directive in Vue. The keys are then styled using the CSS declarations shown above (👆).

Also read: How to Make a Radar Scanner Animation in CSS (Using pseudo-elements)

This code will give you this keyboard layout:

Keyboard layout for temporarily flashing the key pressed

The next step is to make the on-screen keyboard actually react when the user presses a key on their physical keyboard. Since pressing a key is called a keydown event, we need to set up a special “listener” for that event.

We attach this listener to the window object instead of the individual keys for a few reasons. When using these keydown events, the element you’re listening to needs to be focused. Web browsers doens’t allow for more than one element to be focused at the same time.

Also read: How to Read the New Yorker and Atlantic articles for FREE

However, the window object represents the entire browser window, which is always focused as long as the user is on the page. Therefore we attach the listener to the window object.

Adding an Event Listener on the window object

In Vue.js, here’s how you add a “keydown” listener to the window object (this code goes inside the <script> part of your Vue file):

JavaScript
<script>
	mounted() {
		this.attachKeydownListener(); // Call the method on mount
	},
	methods: {
		attachKeydownListener() {
			window.addEventListener("keydown", (e) => {
			// handle event here
			});
		}
	}
</script>

In the mounted hook, we are calling the function which attaches the event listener to the window object. We also could have added the event listener in the mounted hook itself and then created a named function in the methods to handle this logic. Both approaches work.

Now, we only want to process the letter keys, not any other key such as control, alt, space or meta key, so we need a regex which will filter out such keys and allow us to handle only the English alphabet letters.

This is how our attachKeydownListener function looks like when we use the regex filter:

JavaScript
attachKeydownListener() {
	window.addEventListener("keydown", (e) => {
		const alphabetRegex = /^[A-Z]$/;
		const key = e.key.toUpperCase();
		// match only if a letter is pressed
		if (alphabetRegex.test(key)) {
			// apply flashing styles here
		}
	});
}

Now that we have our key, its time to apply the flashing style. For this, we will use the ref attribute provided natively by Vue.

Vue maintains a list of all elements which declare a ref attribute in their declarations which allows us to refer to any element using the array:

JavaScript
this.$refs.id

We have to provide a unique identifier (id) to each element which we want to access using the $refs array. This works just like JavaScript’s native document.getElementById("#id") method.

But first, we need to provide this attribute to all the keys that we are putting on the onscreen keyboard. We have to modify our html code, especially, the v-for directive. This is what the updated code looks like:

JavaScript
<div class="keyboard-container">
		<div class="keyboard-row">
			<span v-for="key in row1" :key="key" :ref="key" class="key">{{ key }}</span>
		</div>
		<div class="keyboard-row">
			<span v-for="key in row2" :key="key" :ref="key" class="key">{{ key }}</span>
		</div>
		<div class="keyboard-row">
			<span v-for="key in row3" :key="key" :ref="key" class="key">{{ key }}</span>
		</div>
	</div>

Observe that we have added :ref="key" attribute to each of the v-for declaration in our HTML template.

Now, all we need to do is use some javascript logic to first add the styles, and then remove them after some time, say 100 milliseconds. The native built-in function setTimeout is perfect for this type of task.

Must read: 4 Awesome tricks to Change the Default Fonts in Excalidraw

Here’s how you can add this code to the attachKeydownListener function to temporarily change the background color to lightgreen: —

JavaScript
attachKeydownListener() {
	window.addEventListener("keydown", (e) => {
		const alphabetRegex = /^[A-Z]$/;
		const key = e.key.toUpperCase();
		// match only if a letter is pressed
		if (alphabetRegex.test(key)) {
			// apply style on that key
			this.$refs[key][0].style.backgroundColor = "lightgreen";		
				setTimeout(() => {
				// remove this style after a 100ms
				  this.$refs[key][0].style.backgroundColor = "whitesmoke";
			  }, 100);
		  }
	  });
  }

Now your onscreen keys will flash when they are pressed. To make the transition smooth, you may also want to declare a transition duration in your CSS styles on keys so that the flashy effect isn’t too harsh for the user:

CSS
.key {
	transition: 0.1s;
}

For a seamless transition, keep the transition duration same as the setTimeout duration, but it isn’t strictly required.

You can see the final program here on Codepen:

<div id="app">
	<div class="keyboard-container">
		<div class="keyboard-row">
			<span v-for="key in row1" :key="key" :ref="key" class="key">{{ key }}</span>
		</div>
		<div class="keyboard-row">
			<span v-for="key in row2" :key="key" :ref="key" class="key">{{ key }}</span>
		</div>
		<div class="keyboard-row">
			<span v-for="key in row3" :key="key" :ref="key" class="key">{{ key }}</span>
		</div>
	</div>

<p> Wanna know how I did this? Check out this <a href="https://csswolf.com/how-to-style-an-element-temporarily-using-vue/"> blog post on CssWolf.com.</a>
</div>
body {
	font-family: whitney, avenir, system-ui;
}

.keyboard-container {
	display: grid;
	gap: 5px;
}

.keyboard-row {
	display: flex;
	justify-content: center;
}

.key {
	padding: 12px 15px;
	border: 1px solid #ccc;
	border-radius: 5px;
	user-select: none; /* Prevent text selection */
	margin: 2px;
	background: whitesmoke;
	transition: 0.1s;
}

#app, p {
	text-align: center;
	padding: 40px;
}
app = Vue.createApp({
	data() {
		return {
			row1: ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"],
			row2: ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
			row3: ["Z", "X", "C", "V", "B", "N", "M"],
			flash: false,
			text: ""
		};
	},
	mounted() {
		this.attachKeydownListener(); // Call the method on mount
	},
	methods: {
		attachKeydownListener() {
			window.addEventListener("keydown", (e) => {
				const alphabetRegex = /^[A-Z]$/;
				const key = e.key.toUpperCase();
				// match only if a letter is pressed
				if (alphabetRegex.test(key)) {
					// apply style on that key
					this.$refs[key][0].style.backgroundColor = "lightgreen";

					setTimeout(() => {
						// remove this style after a 100ms
						this.$refs[key][0].style.backgroundColor = "whitesmoke";
					}, 100);
				}
			});
		}
	}
});

app.mount("#app");

That’s it guys. You can experiment a little more with other approaches, such as adding and detaching a class after a duration, applying animation directly on a keypress or something more innovative.

In my experience, I have found it working excellent for my use case.

Thanks for reading.