Binding CSS Variables in Vue

🗓 February 9, 2020

Background

Binding to style and class in Vue.js can be extremely handy for making styles respond to state in your application. This state might be data fetched through an Ajax request, user input, or any other type of data source that is made available at run time. Binding a CSS Variable to style is a clever way to push this pattern a little bit further, while gaining all the benefits of writing variable driven css.

Simple Style Binding Example

Let's start with a simple (non-css-variable) example to illustrate. Let's say we have an E-book app. To give the user the best reading experience, you allow them to select which font they prefer to use. You might handle this case like so.

<template>
  <div :style="userStyle">
    E-Book Text here
  </div>
</template>

<script>
export default {
  data () {
    return {
      fontOptions: ['Roboto', 'Lobster', 'Comic Sans'],
      userSelectedFont: 'Roboto'
    }
  },
  computed: {
    userStyle () {
      return {
        fontFamily: this.userSelectedFont
      }
    }
  }
</script>

Notice how we capture the user select font in our component state and bind that to the style attribute of the element that renders our text. This is classic Vue style binding and works great!

Now with a CSS Variable

Let's tackle the same use case, but using the CSS variable pattern...

<template>
  <div
    :style="userStyle"
    class="book-text"
  >
    E-Book Text here
  </div>
</template>

<script>
export default {
  data () {
    return {
      fontOptions: ['Roboto', 'Lobster', 'Comic Sans'],
      userSelectedFont: 'Roboto'
    }
  },
  computed: {
    userStyle () {
      return {
        '--user-font-fam': this.userSelectedFont
      }
    }
  }
</script>

<style scoped>
.book-text {
  font-family: var(--user-font-fam);
}
</style>

The difference here is subtle but important. Instead of binding our font choice directly to the font-family style in the style attribute, we bind the choice to a css variable, called --user-font-fam. And --user-font-fam can now be referenced by CSS selectors on elements under our div.

When to Reach For CSS Variable Binding

Most of the time it's not a problem to just bind directly to the style attribute with whatever styling you want, and you don't need to use a CSS variable... but there a few scenarios where the CSS variable can be quite helpful.

1) Reuse

If you find yourself binding the same piece of state to many different style attributes, you might be better served by creating the CSS variable. This will let you author CSS in your normal pattern, but this CSS will benefit from Vue's renowned reactivity system!

2) Psuedo Elements

Sometimes we want to styling things without a style attribute to bind to (like psuedo elements). In these cases, having our CSS variable handy works wonders...

p::first-line {
  color: var(--theme-secondary-color);
}

3) Math

If you're using math to calculate layout it can be nice to stash your math in a css variable... especially of you want CSS to do some of the work. Along these lines:

<template>
  <main :style="bannerStyle">
    <div class="banner">
      Some Info Banner
      <div class="banner-conent">
        banner content
      </div>
    </div>
  </main>
</template>

<script>
export default {
  computed: {
    bannerWidth () {
      const fooWidth = document.getElementById('foo').offsetWidth
      return `${fooWidth / 2}px`
    },
    bannerStyle () {
      return {
        '--banner-width': this.bannerWidth
      }
    }
  }
</script>

<style scoped>
.banner {
  width: var(--banner-width);
}
.banner-content {
  width: calc(var(--banner-width) - 20px);
}
</style>