134

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.

10 Answers 10

131

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

Sign up to request clarification or add additional context in comments.

4 Comments

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.
This is the best answer for 2021 and doing it the way Vue would prefer.
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.
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'));
86

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>

6 Comments

Thanks for your answer.If I want to use css pseudo class, like :hover, what should I do ? Thanks.
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)
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?
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?)
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
|
59

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

4 Comments

ah. Of course they did... why can't we get that love in vue 2 :(
vue 2.7 @AlanSpurlock
how to do it in <script setup>?
Thank you! Also, if you need to access something you can do it with quotes: v-bind('props.color')
47

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

2 Comments

@Geniusintrouble ~95% caniuse.com/css-variables
This great. What if I didn't know the key "background-color" in prior? Say I want to pass a set of effects for hover?
24

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.

1 Comment

isn't that overwriting the style entirely?
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.

1 Comment

No need for a custom component in this case. You could just do <component :is="'style'"></component>
10

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>

Comments

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

Comments

2

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.

Comments

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`
                }
            }
        }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.