Vue.js - Scoped Styles vs CSS Modules
It should not come as a surprise that CSS in modern web development is far from perfect.
Nowadays, projects are usually quite complex and, given the global nature of styles, it’s extremely easy to end up with conflicting styles that override each other or that implicitly cascade down to elements we didn’t consider before.
The most commonly used solution that we use to reduce the main pain points is introducing the BEM (Block Element Modifier) methodology. However, it addresses only a small part of the bigger problem.
Fortunately for us, the community already developed solutions that can help us deal with the problem more thoroughly. You might’ve already heard about CSS Modules, Styled Components, Glamorous or JSS - these are just a few of the most popular tools that we can add to our projects today. If you're interested in the topic, you can check this post - Indrek Lasn explains the whole CSS-in-JS idea there very thoroughly.
Every new Vue.js application created by vue-cli comes with two great built-in solutions: Scoped CSS and CSS Modules. Both of them have some pros and cons, so let’s take a closer look and see which solution might be a better fit for your case.
In order to get scoped styles working, we just have to add a scoped attribute to the
It will apply our styles only to elements in the same component by using PostCSS and transforming the above example to the following:
As you can see, it requires no effort at all to have nicely scoped styles, and it also handles scoping tags’ styles in the same way.
Now, if you need to - let’s say - change the width of a component in a specific view, you can apply an extra class to it and style it as you normally would with all the benefits of scoped styles:
It will transform to:
Once again - with no extra effort you’ve got full control over the layout.
However, be aware that this feature was introduced with one drawback - if your child component's root element has a class that also exists in the parent component, the parent component's styles will leak to the child. You can check out this CodeSandbox to get a better understanding of the problem.
Although it is not recommended and should be avoided - there are cases where we need to style something deeply inside our child component. For the sake of simplicity, let’s assume that our parent component should be responsible for the styling header of the BasePanel component. In scoped styles, the
>>> combinator (also known as
/deep/) comes in at this point.
It will be transformed to:
Plain and simple, huh? But be aware that we just lost the encapsulation. Any
.title class that will be used inside this component (even implicitly by a grandchild) will be affected by these styles.
CSS Modules gained their popularity due to the React community that quickly adopted this technology. Vue.js takes it to another level by combining its power with simplicity of use and out-of-the-box support by using vue-cli.
Now let’s look at how we can use it:
Instead of using the
scoped attribute, we use
module. It will tell vue-template-compiler and vue-cli's webpack configuration to use the proper loaders to process this part and generate the following CSS:
What makes it so special and different from scoped styles is that all the created classes are accessible via the
$style object inside the component. So in order to apply this class we have to use class binding:
This will generate the following HTML and related styles:
The first benefit is that by looking at this element in our HTML we immediately know which component it belongs to. Secondly, everything becomes very explicit and we have full control - no magic whatsoever. However, we have to be careful while styling HTML tags, as they land in the final CSS as-is, as opposed to scoped styles, where even plain tags are scoped by the unique data attribute.
Similar to the second example from scoped styles, let’s see how we can style the component in a certain context:
It will transform to:
It simply gets the job done, without any surprises! Moreover, because all classes are available through the
$style object we can now pass them however deep we want using props, making it super easy to use a class in any place of the child component:
CSS Modules have great interoperability with JS, and they do not limit you to classes. Using
:export keyword, we can also export additional things to our
Imagine you have a chart to develop - you can keep your colour variables in CSS, and additionally export them for use in your component:
I only scratched the surface here - the CSS Modules concept is much broader and I encourage you to check out the full specification to know more.
Both solutions are very simple, easy to use and, to an extent, solve the same issue. Which one should you choose then?
Scoped styles require literally no extra knowledge to use and feel comfortable with. Their limitations also make them simple to use, and they're capable of supporting small to mid-sized applications.
However, when it comes to more complex scenarios and bigger apps, we probably want to be more explicit and have more control over what’s going on in our CSS. Even though using the
$style object multiple times in a template might not look so sexy, it’s a small price to pay for the safety and flexibility it allows. We also get easy access to our variables (like colours or breakpoints) in JS, without having to keep separate files in sync.
Which one do you use? And why? Feel free to share any additional scenarios you encountered along the way!