Vue Scoped CSS and Style Patterns

With vue-cli you get two out-of-the-box options for scoping styles to a component. This is useful for constraining styles locally to that component without affecting others. The two options are 1) scoped css and 2) css modules. You can also use CSS-in-JSS libraries as well.

Scoped CSS

By adding scoped to the <style> tag, a PostCSS transform is applied that gives it a data attribute like data-v-c9a2660. This gives us css specificity for styling just that component.

Your CSS gets transformed from this:

<style scoped>
.example {
  background-color: blue;
}
</style>

Into this:

<style>
.example[data-v-c9a2660] {
  background-color: blue;
}
</style>

Your markup gets transformed from this:

<template>
  <div class="example">Example</div>
</template>

Into this:

<template>
  <div class="example" data-v-f3f3eg9>Example</div>
</template>

Caveats to Scoped CSS

  • It can be easy to leave out scoped and have your styles leak to other components
  • Related, you end up using long css class names as a secondary measure to prevent leaking styles among components.
  • Deep selectors of child components can still be done by using >>>:
    <style scoped>
      .a >>> .b { /* ... */ }
    </style>
    
  • Yes you still need css selectors vs html tag names for performance reasons: p { color: red } will be many times slower vs .example { color: red }

Example with prop controlling styles

Here’s an example of using props to control component styles with scoped CSS. We use a computed property classes that list out all the additional css classes to apply to the root element.

<script>
export default {
  props: {
    linkable: {
      type: Boolean,
      default: false
    },
    raised: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    classes() {
      return {
        'example--linkable': this.linkable,
        'example--raised': this.raised,
      }
    }
  }
}
</script>

<template>
  <div class='example' :class='classes'>
    Example Styles
  </div>
</template>

<style scoped>
.example--raised {
  box-shadow: 0px 0px 12px -4px rgba(0,0,0,0.75);
}

.example--linkable {
  cursor: pointer;
}
</style>

CSS Modules

CSS Modules is a popular system for modularizing and composing CSS.

By adding module to the <style> tag, vue-loader allows us to access the styles via a computed property on the computed as $style. The return value of which is a string of that css class name, usually something obfuscated like _317VTbw-z_NrMkQSHwnHYD_1. This truly allows for non-conflicting styles and modularity.

Here’s how you would use css modules in your component:

<template>
  <div :class='$style.example'>Example</div>
</template>

<style module>
.example {
  background-color: blue;
}
</style>

Note how the class attribute is evaluated to use the $style component property that has been added by using module. The actual rendered markup will appear something like:

<div class="_317VTbw-z_NrMkQSHwnHYD_1">
  Example
</div>

Example with prop controlling styles

Similar example as before but with css modules:

<script>
export default {
  props: {
    linkable: {
      type: Boolean,
      default: false
    },
    raised: {
      type: Boolean,
      default: false
    }
  },
  computed: {
    classes() {
      return {
        [this.$style.linkable]: this.linkable,
        [this.$style.raised]: this.raised,
      }
    }
  }
}
</script>

<template>
  <div class='example' :class='classes'>
    Example Styles
  </div>
</template>

<style module>
.raised {
  box-shadow: 0px 0px 12px -4px rgba(0,0,0,0.75);
}

.linkable {
  cursor: pointer;
}
</style>

Caveats to CSS Modules

  • You may start introducing more unique css classes, id’s or data attributes for testing, automation, e2e.
  • There is no warning when an undefined style is used, such as this.$style.missing. It will just return empty class name.
  • If you want to maintain convention of kebab-case for css class names, syntax will look a bit jarring. For example you’d have [this.$style["example-card-style"]]: true

CSS-in-JS libraries

Many options here. But here’s a quick example with styled-components

<script>
import styled from 'vue-styled-components';

const StyledCard = styled.div`
  padding: 2em;
  font-size: 0.8em;
  background: lightgrey;
  border: 1px solid grey;
`;

export default {
  components: {
    StyledCard
  }
}
</script>

<template>
  <StyledCard>
    Hello I'm a styled card!
  </StyledCard>
</template>

Summary

You have several options to style components in your vue application. CSS modules typically has less headaches in the long-run, as it is very good at encapsulating styles. Whereas scoped css often times leaks in hard-to-debug manners as your application grows.

Additional Reading