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
- Official Vue documentation on: