In this article, I will share with you the process of discovering one of the not-so-common features of Vue.js, the injection of CSS variables from the component's script. Let's get a headache together and learn some Vue!
So. A few days ago, I was faced with an interesting challenge at work. The need to pass down some CSS style attributes into my <style>
tags for controlling a couple of different properties with the same value, as well as performing some calc
results.
After digging around Google and Github issues, I found a couple solutions that gave me a deeper understanding of Vue, and was definitely a fun little challenge that I want to share with you. 😋
For the sake of example, let’s pretend you have a button component that you want to pass some properties that will control its height and the background color.
In reality, what I was trying to do was a lot more complex since it involved a bunch of SCSS calculations and CSS animations, but I like keeping my articles clutter free - so bear with me.
<template>
<button :style="btnStyles">My button</button>
</template>
<script>
export default {
name: 'SimpleButton',
props: {
bgColor: {
type: String,
default: "#0099CC"
},
height: {
type: Number,
default: 100
}
},
computed: {
btnStyles() {
return {
"background-color": this.bgColor,
height: `${this.height}px`
};
}
}
};
</script>
Alright, so this would be a SimpleButton
class, super useless, and as UGLY as I managed to make it. Just kidding, I’m actually a UX designer undercover. 👩💻
Coming back to the simplicity of the example, however, what I’m trying to do here is make this button’s CSS controllable by properties. I’m fully aware that this example is flawed, and that I could just simply be binding attributes into classes - but as stated, I don’t want to overcomplicate things.
It does, on the other hand, illustrate my first approach to the problem - put everything in an inline style tag. SURE, ok this works just fine, but it starts to become really verbose and complicated to follow once you have N
levels of nested divs
each, with their own different tags inside that need to be dynamic. Even more so if you’re trying to get SCSS to work in your favor with some calculations, mixins, functions, etc.
So at this point, I got my stubborn hat on and decided I had to find a solution that would allow me to pass in these variables into the <style>
.
Vue 1.0 had the ability to somewhat interpolate variables into the <style>
tag, and would allow you to set up CSS variables like so: --bg-color: {{ bgColor }};
However this came with a huge cost, because on every re-render Vue was having to do crazy things like recompiling these styles. Needless to say, it doesn’t work like this today. <style>
tags are static, and once the component is mounted, that’s that.
At this point, stubborn was the name of the game. So I got digging and found a couple posts that pointed me in a similar direction. Create CSS variables on the inline style
and then use them in the SCSS. Yeah…
So how do we do this?
Let’s start by first switching some things around on our previous example.
Go to the computed props, and replace the btnStyles
with this new cssVars
property.
computed: {
cssVars() {
return {
'--bg-color': this.bgColor,
'--height': this.height + 'px'
}
}
}
Alright, so now that we are dynamically generating these variables - how do we use them? Simple, let’s pass them to the <button>
through the :style
binding like we were doing before.
<button :style="cssVars">My button</button>
Yaaay…? Don’t worry, SOMETHING happened. Go to your developer tools and inspect the button
, you’ll see in the element.style
tab:
Go ahead and add these prop-created CSS variables into your style
block for the button component.
<style scoped>
button {
color: var(--text-color);
background-color: var(--bg-color);
height: var(--height);
}
</style>
Go ahead and run this in your browser, tada ~!
Alright so the theory is really nice, but how do we actually test the reactivity of the CSS variables?
There are two ways to do it with what we currently have: mount the button
component into your app, and pass in different props.
Or, for the sake of simplicity, we’re going to add a local data
state so we can modify it on Vue developer tools.
Add this to your button
logic.
data() {
return {
textColor: "blue"
};
},
Finally, don’t forget to add the color
property to your styles
.
<style scoped>
button {
color: var(--text-color);
background-color: var(--bg-color);
height: var(--height);
}
</style>
Alright, now that we’re all set up - go to your browser and open the app. You’ll see that your button’s text is blue
, as expected, since that is what we first set up as textColor
on our state.
Now go into your Vue dev tools, and click on the property and change it to red
or your color of choice. You’ll immediately see that the color of the button’s text changes as well.
At this point you’re either fascinated by the endless possibilities of this approach, (well maybe not endless - it’s quite a niche solution to a very small handful of problems), or you are completely and utterly confused.
If you’re one of those in the second group, and you’re thinking that this whole mess could have been solved by :style
or :class
bindings, you’re absolutely right. Congratulations! However, as I mentioned, there are very specific SCSS scenarios where this starts becoming handy.
Example? How about calculating a menu’s position based on a the size of the menu and the internal icon’s size?
'--menu-position': ( ( ( this.menuSize * 2) - this.iconSize ) / -2 ) + 'px',
Another example could be using them to do some CSS math. (🤢)
height: calc(var(--height) * 10);
Frankly, this whole endeavor was fun to investigate and apply to an actual project, and I’m sure I’ll run into a moment in my life where I’ll be glad I have this handy tool under my belt. So on that note, I hope at the very least you were entertained 😅.
Want to learn more about creating great web apps? It all starts out with Kendo UI - the complete UI component library that allows you to quickly build high-quality, responsive apps. It includes everything you need, from grids and charts to dropdowns and gauges.
Marina Mosti is a frontend web developer with over 18 years of experience in the field. She enjoys mentoring other women on JavaScript and her favorite framework, Vue, as well as writing articles and tutorials for the community. In her spare time, she enjoys playing bass, drums and video games.