VueJs Directives: Class and Style Binding
Created on April 5, 2022.
For the most part up to this point, we have been dealing with just laying out the markup of the templates in our Vue applications. In this post, we are going to deviate a bit and focus on styling of our HTML markups.
Not only do we style our HTML documents to make them look better but also notify the users of changes in app states. Sometimes we spin loaders to show that something is being processed in the background or show specific colored notifications with accompanying messages to signal process completion or failure.
Binding Options
We can package component styles in named selectors, i.e. the id and class attributes then change the selectors being bound to our components on state changes. We can also in-line our styles within HTML elements with the style attribute and directly change the styles when the state changes.
The choice to go with one or the other depends on size and complexity of the project being worked on. In complex apps the earlier method is recommended as it makes the code readable and easy to maintain.
The binding of the selectors or the in-line style attribute is done with the v-bind directive.
Binding Using In-line Styles
Let’s start with a simple example.
<html>
<div id="app">
<div :style="`background-color: ${color}; height: ${size}; width: ${size}; padding: 10px`">
I am a box
</div>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
size: 100,
color: 'cyan'
}
}
}).mount("#app");
</script>
</html>
As we can see in the above example, binding in-line styles with v-bind while using the shortcode :
, just as how we would bind any other attribute using this directive.
Vue also allows us to bind both the style and class attributes in addition to them existing previously on the same element, this allows the separation of the unchanging styles such as padding: 10px
above and the bound styles. This separation helps us maintain clean code.
Add the following div block to the above template and compare the two boxes.
<div style="padding: 10px" :style="`background-color: ${color}; height: ${size}; width: ${size}`">
I'm box 2
</div>
Binding Using the Class Attribute
Let’s see a style binding example using an element class attribute.
<html>
<style>
.bulb{
width: 100px;
height: 100px;
border: 3px solid gainsboro;
border-radius: 50%;
}
.on{
background-color: yellow;
}
.off{
background-color: cornsilk;
}
</style>
<div id="app">
<div class="bulb" :class="isOn ? 'on' : ''">
</div>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
isOn: true
}
}
}).mount("#app");
</script>
</html>
In the above example, we are changing the state of the light bulb between on and off depending on the state of the isOn variable.
When the isOn variable is truthy, the on
class gets appended to the existing class="bulb"
resulting to class="bulb on"
, consequently applying the .on
class styles included in our style block above to our bulb, when otherwise it is replaced with an empty String removing those styles.
Try changing the value of isOn to see the resulting changes.
Binding Classes and Styles to Objects
We can opt to further simplify the binding of classes and in-line styles by using Objects in cases where we have an on and off state to our styling such as with the light bulb example above.
Let’s modify that example using this method.
<div id="app">
<h2>Binding to Objects</h2>
<div class="bulb" :class="{on: isOn}">
</div>
<br>
<div class="bulb" :style="{backgroundColor: onColor}">
</div>
</div>
let app = Vue.createApp({
data() {
return {
isOn: true,
onColor: 'yellow'
}
}
}).mount("#app");
We can pass as many properties as possible using the Object method.
<html>
<style>
.bulb{
width: 100px;
height: 100px;
border: 3px solid gainsboro;
background-color: cornsilk;
}
.round {
border-radius: 50%;
}
.on{
background-color: yellow;
}
</style>
<div id="app">
<h2>Binding to Objects with multiple properties</h2>
<div class="bulb" :class="{round: isRound, on: isOn}">
</div>
<br>
<div :style="{width: size + 'px', height: size + 'px', backgroundColor: offColor, 'border-radius': 50 + '%', borderWidth: 3 + 'px', borderColor: 'gainsboro', borderStyle: 'solid'}">
</div>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
isOn: true,
isRound: false
}
}
}).mount("#app");
</script>
</html>
While binding in-line styles using Objects, we name CSS properties inside the Objects using their exact Strings or by using the camel case convention. Refer to backgroundColor
and 'border-radius'
in the example above.
We can remove the in-line Object from our element’s bound class attribute and place it inside the data property, moving the logic from the template completely, only to bind the resulting Object to it. Given this possibility, we can also use computed properties in style binding.
Let’s demonstrate this in the following example.
<html>
<style>
.bulb{
width: 100px;
height: 100px;
border: 3px solid gainsboro;
background-color: cornsilk;
}
.round {
border-radius: 50%;
}
.on{
background-color: yellow;
}
</style>
<div id="app">
<h2>Binding to Objects with data properties</h2>
<div class="bulb" :class="squareBulbOn">
</div>
<br>
<div :style="roundBulbOff">
</div>
<h2>Binding to Objects from computed properties</h2>
<div class="bulb" :class="fenceLights">
</div>
<br>
<div :style="lawnLights">
</div>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
size: 100,
offColor: 'cornsilk',
isOn: true,
isRound: false,
squareBulbOn: {
on: true,
round: false
},
roundBulbOff: {
width: 100 + 'px',
height: 100 + 'px',
backgroundColor: 'cornsilk',
borderRadius: 50 + '%',
borderWidth: 3 + 'px',
borderColor: 'gainsboro',
borderStyle: 'solid'
},
dayLight: true
}
},
computed: {
fenceLights(){
return { on: this.dayLight, round: false };
},
lawnLights(){
return {
width: `${this.size}px`,
height: `${this.size}px`,
backgroundColor: this.dayLight ? this.offColor : 'yellow',
borderRadius: 50 + '%',
borderWidth: 3 + 'px',
borderColor: 'gainsboro',
borderStyle: 'solid'
};
}
}
}).mount("#app");
</script>
</html>
As you can see, we have made our template code shorter, easy to read, and maintainable while still getting the same result.
Binding Classes and Styles to Arrays
In addition to binding using Objects, we can also use Arrays to bind element styles and classes.
Arrays give us beyond the Boolean state operations we get to use to define classes we get with Objects, especially when targeting to maintain a minimal code-base. We get to define several states within a single data property which is a more realistic CSS stance since most properties happen to have more than two states. We can also use Objects within Arrays, hence further minimizing the logic within our templates.
Let’s observe the following example.
<html>
<style>
.bulb{
width: 100px;
height: 100px;
border: 3px solid gainsboro;
background-color: cornsilk;
}
.round {
border-radius: 50%;
}
.on{
background-color: yellow;
}
</style>
<div id="app">
<h2>Binding to Arrays</h2>
<div :class="[bulb, roundBulb, lightsOn]">
</div>
<br>
<div :class="[bulb]" :style="[{backgroundColor: lightsOffColor}, {'border-radius': 50 + '%'}]">
</div>
<h2>Binding to Arrays with data properties</h2>
<div class="bulb" :class="squareBulbOn">
</div>
<br>
<div :style="roundBulbOff">
</div>
<h2>Binding to Arrays from computed properties</h2>
<div :class="fenceLights">
</div>
<br>
<div :style="lawnLights">
</div>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
size: 100,
bulb: 'bulb',
roundBulb: 'round',
lightsOn: 'on',
lightsOffColor: 'cornsilk',
lightsOnColor: 'yellow',
squareBulbOn: [
{on: true},
{round: false}
],
roundBulbOff: [
{width: 100 + 'px'},
{height: 100 + 'px'},
{backgroundColor: 'cornsilk'},
{borderRadius: 50 + '%'},
{borderWidth: 3 + 'px'},
{borderColor: 'gainsboro'},
{borderStyle: 'solid'}
],
dayLight: true,
fancyLights: false
}
},
computed: {
fenceLights(){
return [ this.bulb, {on: this.dayLight}, 'border-radius: 50%' ];
},
lawnLights(){
return [
`width: ${this.size}px`,
`height: ${this.size}px`,
'background-color: ' + (this.dayLight ? this.lightsOffColor : this.lightsOnColor),
{borderRadius: 50 + '%'},
{borderWidth: 3 + 'px'},
{borderColor: 'gainsboro'},
{borderStyle: 'solid'}
];
}
},
mounted(){
const dayAndNight = () => {
if(this.fancyLights){
this.dayLight = !this.dayLight
} else {
clearInterval(toggleId);
}
}
let toggleId = setInterval(dayAndNight, 500)
}
}).mount("#app");
</script>
</html>
Looking closer at the example above, we realize that binding with Arrays, enables us to combine all of what we’ve learned in this post and put it together to get desired results.
Change the value of fancyLights in the example above to see some cool lights in action.
Next
Vue.js directives: Form binding and event handling
Previous