98

I'm new to vue.js. Here is my problem:

In a *.vue file like this:

<template>
  <div id="a">
  </div>
</template>

<script>
  export default {
    name: 'SquareButton',
    props: ['color']
  }
</script>

<style scoped>
    #a {
      background-color: ?
    }
<style>

How can I use the props color in background-color: (where is a ? now).

Thanks.

MingWen
  • 983
  • 1
  • 6
  • 5

8 Answers8

110

You actually can!

You should define the CSS variables in a Computed Property, then call the computed property as a style attribute to the element that will require the CSS variable, and finally you may use the variable within the tags at the bottom of your document.

new Vue({
  el: '#app',
  data: function() {
    return {
      baseFontSize: 1,
      bgHoverColor: "#00cc00",
      hoverContent: "Hovering!"
    }
  },
  computed: {
    cssProps() {
      return {
        '--hover-font-size': (this.baseFontSize * 2) + "em",
        '--bg-hover-color': this.bgHoverColor,
        '--hover-content': JSON.stringify(this.hoverContent)
      }
    }
  }
})
div {
  margin: 1em;
}

div.test:hover {
  background-color: var(--bg-hover-color);
  font-size: var(--hover-font-size);
}

div.test:hover::after {
  margin-left: 1em;
  content: var(--hover-content);
}
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app" :style="cssProps">

  <div>Hover text: <input type="text" v-model="hoverContent"></div>
  <div>Hover color: <input type="color" v-model="bgHoverColor"></div>

  <div class="test">Hover over me</div>
</div>

Or have a look here: https://codepen.io/richardtallent/pen/yvpERW/
And here: https://github.com/vuejs/vue/issues/7346

j3ff
  • 5,139
  • 7
  • 39
  • 50
Yuri M
  • 1,141
  • 1
  • 5
  • 2
  • 9
    Putting your code on SO would make this a much better and complete answer. – DrCord Sep 23 '19 at 16:13
  • 1
    I found this to be the best answer by any means, even if you **need both dynamic values in your css and css features that can't be applied to a style attribute** which was emphasized by @gwildu as the only reason to use his answer. In my opinion this seems to be the neatest solution and actually the right way to do it in vue. – Jacek Dziurdzikowski Jan 25 '20 at 18:14
  • This is the best answer for 2021 and doing it the way Vue would prefer. – DrCord Feb 12 '21 at 16:02
73

You don't. You use a computed property and there you use the prop to return the style of the div, like this:

<template>
  <div id="a" :style="style" @mouseover="mouseOver()">
  </div>
</template>

<script>
  export default {
    name: 'SquareButton',
    props: ['color'],
    computed: {
      style () {
        return 'background-color: ' + this.hovering ? this.color: 'red';
      }
    },
    data () {
      return {
        hovering: false
      }
    },
    methods: {
      mouseOver () {
       this.hovering = !this.hovering
      }
    }
  }
</script>

<style scoped>
<style>
Potray
  • 1,808
  • 14
  • 22
  • 4
    Thanks for your answer.If I want to use css pseudo class, like `:hover`, what should I do ? Thanks. – MingWen Mar 18 '17 at 08:34
  • 2
    You could use a mouseover event to trigger a change in data, and use that data property in the style computed property. I edited the example so you could see an example (it's not tested though, so perhaps there is something to fix, but I think you can get the idea) – Potray Mar 18 '17 at 09:00
  • what if I'm using vuetify and the element is dynamically created, I can't bind to an element I haven't explicitly written in the template markup? – Akin Hwan Jul 23 '18 at 16:19
  • Is the returned style also scoped or will it leak to child elements? (My guess is that it's global, because PostCSS is required for the transformation. Any way to scope it?) – Sampo Dec 04 '18 at 20:38
  • How do you handle css paths? For example, if I want to compute a background-image property that would normally be `background-image: url('~@/path/to.image')`, if I do it your way, it ends up with that path on the actual frontend site and doesn't work – Tobias Feil Mar 06 '20 at 15:35
  • for `background-image: url(...)` use the required() as seen in [this answer](https://stackoverflow.com/a/56569951/4567511). Has taken me ages to find that out – dnl.re Feb 05 '21 at 15:32
35

As we are in 2020 now, I suggest using this trick with a css function called var

<template>
    <div id="a" :style="cssVars"></div>
</template>

<script>
export default {
    props: ['color'],
    computed: {
      cssVars () {
        return{
          /* variables you want to pass to css */
          '--color': this.color,
        }
    }
}
<script>

<style scoped>
#a{
    background-color: var(--color);
}
</style>

This method is very useful because it allows you to update the passed values through css later on (for example when you apply hover event).

credit

Quinten Cabo
  • 448
  • 5
  • 15
Emad Ahmed
  • 451
  • 4
  • 4
19

Why not just use :style prop in this way:

<template>
  <div :style="{ backgroundColor: color }">
</template>

<script>
export default {
  props: {
    color: {
      type: String,
      default: ''
    }
  }
}
</script>

Make sure you define css properties in camelCase style.

phen0menon
  • 2,129
  • 2
  • 16
  • 36
16

If you need css that can't be applied by a style attribute like pseudo classes or media queries, what I do is the following:

Create a globally available style component when initializing Vue (you need it as otherwise you run into linting issues). It creates a style tag that simply renders the content in the slot:

I would only use this if you really need both dynamic values in your css and css features that can't be applied to a style attribute.

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false
Vue.component('v-style', {
  render: function(createElement) {
    return createElement('style', this.$slots.default)
  }
})

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

Then use it at the top of your template like this and you get the full JavaScript scope of your component and the full css syntax combined:

<template>
  <v-style>
    @media screen and (max-width: 820px) {
      .gwi-text-media-{{ this.id }} {
        background-image: url({{ mobileThumb }});
      }
    }
  </v-style>
</template>

It seems a bit hacky to me, but it does it's job and I would rather go like this in some cases than having to add additional JS for mouse-over or resize events that have a big potential to slow down your application performance.

gwildu
  • 2,872
  • 1
  • 16
  • 21
11

I know we're talking vue 2 here, but in case anyone from vue 3 lands in this question (like I did), vue 3 introduced a much cleaner way to do this:

<template>
  <div id="a">
  </div>
</template>

<script>
  export default {
    name: 'SquareButton',
    props: ['color']
  }
</script>

<style scoped>
    #a {
      background-color: v-bind(color);
    }
<style>

What Vue actually does behind the scenes is the same "introducing css variables through component's style process", but it sure looks much better on the eyes now.

Documentation source: https://v3.vuejs.org/api/sfc-style.html#state-driven-dynamic-css

GuiMendel
  • 115
  • 1
  • 6
4

You could utilise the CSS var(--foo-bar) function. It is also useful if you are trying to pass an asset that has its own dynamic path, like Shopify does.

This method also works for styling the :before and :after elements as they refer back to the style applied on the owner element.

Using the original post example for passing a colour:

<template>
  <div
    id="a"
    :style="{ '--colour': color }">
  </div>
</template>

<script>
  export default {
    name: 'SquareButton',
    props: ['color']
  }
</script>

<style scoped>
  #a {
    background-color: var(--colour);
  }
<style>

Using the original post example for passing an URL:

<template>
  <div
    id="a"
    :style="{ '--image-url': 'url(' + image + ')' }">
  </div>
</template>

<script>
  export default {
    name: 'SquareButton',
    props: ['image']
  }
</script>

<style scoped>
  #a {
    background-url: var(--image-url);
  }
<style>

Source

Crimbo
  • 1,145
  • 1
  • 10
  • 39
2

Vue 3 added new way of binding styles, so now you can easily bind your props to css properties.

Read source: https://learnvue.co/2021/05/how-to-use-vue-css-variables-reactive-styles-rfc/

<template>
  <div>
    <div class="text">hello</div>
  </div>
</template>

<script>
  export default {
    data() {
        return {
            color: 'red',
        }
    }
  }
</script>

<style>
  .text {
    color: v-bind(color);
  }
</style>
Freestyle09
  • 3,404
  • 4
  • 38
  • 72