119

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
  • 1,203
  • 2
  • 9
  • 5

10 Answers10

125

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,719
  • 8
  • 38
  • 51
Yuri M
  • 1,301
  • 1
  • 8
  • 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
  • Sweet. This even stays reactive. If you still using Vue2 this is the way to go. Otherwise Vue3's v-bind in styles is your alternative. – vir us Dec 16 '22 at 19:43
  • Is it also possible to pass a url to a css attribute and then use `url()` function around the passed value? I'm basically trying something like `background-image: url(var('--bg'));` – dotNET May 08 '23 at 06:26
81

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,928
  • 16
  • 23
  • 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
44

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 C
  • 660
  • 1
  • 8
  • 18
Emad Ahmed
  • 540
  • 4
  • 5
39

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://vuejs.org/api/sfc-css-features.html#v-bind-in-css

Andy
  • 4,901
  • 5
  • 35
  • 57
GuiMendel
  • 539
  • 4
  • 9
23

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,354
  • 2
  • 17
  • 36
17

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
  • 3,621
  • 1
  • 17
  • 22
9

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
  • 4,894
  • 8
  • 52
  • 83
5

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

ckhatton
  • 1,359
  • 1
  • 14
  • 43
1

As a note for myself and future readers, if you're using Vue 3's Composition API with <script setup lang="ts">, the following syntax works correctly:

In the script section, define your prop(s):

const props = defineProps<{
  myprop: string;
}>()

In the styles section:

.some-class {
  background-image: v-bind('props.myprop');
}

Note that the prop name is enclosed in quotes.

dotNET
  • 33,414
  • 24
  • 162
  • 251
0

A simple component to add css styles in Vue 2, using <style> tag

<template>
    <span class="embed-style" style="display: none" v-html="cssText"></span>
</template>

<script>
export default {
    name: 'EmbedStyle',
    props: {
        rules: {
            type: Array,
            default() {
                return []
            }
        }
    },
    computed: {
        cssText() {
            let txt = ' ';
            this.rules.forEach(rule => {
                txt += `${rule.selector} { `;
                for (const prop in rule.props) {
                    txt += `${this.fromCamelCase(prop)}: ${rule.props[prop]}; `;
                }
                txt += ' } ';
            })
            return `<style>${txt}</style>`;
        }
    },
    methods: {
        fromCamelCase(str) {
            let newStr = '';
            for (let l = 0; l < str.length; l++) {
                if (/[A-Z]/.test(str.charAt(l))) {
                    const lower = str.charAt(l).toLowerCase();
                    newStr += `-${lower}`;
                } else {
                    newStr += str.charAt(l);
                }
            }
            return newStr;
        }
    }
}
</script>

Then we use this way:

<template>
  <embed-style :rules="[cssRules]" />
...
    
computed: {
        cssRules() {
            return {
                selector: '.toaster .toast',
                props: {
                    animationDuration: `${this.duration}ms !important`,
                    transitionDuration: `${this.duration}ms !important`
                }
            }
        }
}
user3504541
  • 81
  • 1
  • 2