In the past few years using Vue as my favorite framework, I’ve come across many people who believed that the only way to style a component was by using CSS inside the style
tag within the .vue
file itself. Some people even reported disliking Vue solely because of this!
So, to clarify this somewhat "cloudy" topic, come along and let’s explore the different approaches to styling Vue components.
Table of contents
Global styles
Scoped styles
CSS Modules
When to use each one
Global styles
As the name suggests, global styles are available throughout the entire application and are usually imported in the main.ts
file inside your project’s /src
folder.
When you start a Vue application, a global file is already configured and imported:
import './assets/main.css'
You can create as many global files as you want and import them directly in your main.ts
. Alternatively, you can import your global styles into a single .css
file (or .scss
, .less
, etc.) and then import that single file into your main.ts
:
// assets/styles.scss
@import "base/reset";
@import "base/variables";
@import "base/typography";
@import "components/buttons";
@import "components/table";
@import "pages/home";
// main.ts
import './assets/styles.scss'
❓ "But don’t I need to have a style
tag in my .vue
file?"
👉🏽 No! The Vue SFC model only requires you to have the template
that will be rendered, with both script
and style
being optional.
<template>
to="/">Home
to="/about" class="txt-tomato">About
template>
If your project is small and/or you use strategies like Atomic Design or BEM, using global styles will work very well for you. However, it’s important to be cautious, as poorly managed global CSS can cause styles to "leak" between components and pages.
Back to Table of contents
Scoped styles
A Vue project comes by default with a tag in the
App.vue
file. Using the scoped
attribute means that the CSS is scoped, meaning that all the CSS declared within the tag will be exclusive to the component.
⚠️ Using the
style
tag without thescoped
attribute applies the styles globally, so be cautious with this detail!
<template>
/>
to="/">Home
to="/about" class="example">About
template>
<style scoped>
.example {
color: darkorchid;
}
style>
<template>
class="example">You did it!
template>
We created a class example
only in the App.vue
file. However, we also use this same class in another file, HelloWorld.vue
. As a result, the darkorchid
color will be applied only to App.vue
, even though HelloWorld.vue
is its child component.
❓ What if I wanted to reuse this class in the child component? Do I need to declare it in a global CSS?
👉🏽 Well, that’s one option... But Vue’s scoped
attribute allows the classes to become "deep" with the :deep()
selector, enabling you to pass...
Scoped styles from parent to child
<template>
class="example">
/>
to="/">Home
to="/about" class="example">About
template>
<style scoped>
/* And here we use the :deep() selector to apply inheritance from the child component */
.example :deep(.example) {
color: darkorchid;
}
style>
<template>
class="example">You did it!
template>
But if you really want to follow the approach of making this class global starting from a specific component, scoped
also provides the :global()
selector:
<style scoped>
:global(.example) {
color: darkorchid;
}
style>
This way, we make the .example
class available to any other component in the application. Personally, I avoid using :global()
, preferring to keep global styles in main.css
for better organization and readability.
⚠️ Avoid using
:global()
as much as possible, because this selector increases the style specificity, overriding scoped styles.
There’s also the possibility of using two style
tags in a component — one for global styles and another for scoped styles (but that might be a bit too much 😅).
<style>
/* global styles */
style>
<style scoped>
/* scoped styles */
style>
✒️ Learn more: Scoped CSS (Vue Docs)
Back to Table of contents
CSS Modules
There are two different ways to use CSS Modules with Vue:
- by using the
inside the component itself (SFC); or
- by creating a
.module.css
file (or.module.scss
,.module.less
, etc.) and importing it into the component.
CSS Modules (SFC)
Using CSS Modules inside the component itself is very simple: just use the tag. From there, all CSS created within this scope will be accessible in the
through the
$style
object. Let’s check an example:
<template>
:class="$style.example">You did it!
template>
<style module>
.example {
color: tomato;
}
style>
Here we are using the module
attribute and accessing the example
class in the template
through the $style
object. So, even though we also have an example
class in App.vue
, they will have independent styles:
This approach allows for a more harmonious code structure, keeping the logic and styles of a component isolated in a single file. It also makes it easier for the developer to see the relationship between the template and the styles.
However, reusing these classes becomes more difficult, and it can also make your file very lengthy — a potential issue we can solve with...
CSS Modules in a Separate File
...which improves the separation of concerns and makes it easier to reuse styles across multiple components, also encouraging the use of more complex CSS.
To test this, let's create a file in our /assets
folder called example.module.css
:
.example {
color: yellow;
}
This file must be imported into every component where you want to use the example
class. Let's import it into our HelloWorld.vue
:
<script setup lang="ts">
// Importing the CSS Module as styles -->
import styles from '@/assets/example.module.css'
script>
<template>
:class="styles.example">You did it!
template>
<style module>
/* Even with this module declared in the file,
the "tomato" color from this 'example' class
will not be applied */
.example {
color: tomato;
}
style>
When a CSS Module is processed, each class from each module receives a unique hash in the final build, ensuring there are no conflicts with other classes, even if they have the same name in different files:
And the coolest part of using CSS Modules with TypeScript? Autocomplete! 🤩
✒️ Learn more: CSS Modules (Vue Docs)
Back to Table of contents
When to Use Each One?
Global styles will always exist in an application. Use them for general-purpose elements, like typography, colors, and custom spacing.
Use Scoped when:
- You have simple and isolated components;
- You prefer a simpler syntax;
- You don't need programmatic access to the classes.
Use CSS Modules when:
- You need to access classes via JavaScript;
- You want to share styles across components;
- You're working on larger projects with many developers.
The important thing to understand is that there isn't a one-size-fits-all solution when it comes to styling in Vue. Each method has its purpose and context:
Global styles are used to define the visual foundation of your project, while scoped
styles help maintain organization and prevent conflicts in specific components. CSS Modules, on the other hand, are type-safe when used with TypeScript, which can be a big plus for developer experience in larger, more complex projects.
Vue offers complete flexibility when it comes to styling. Instead of being limited to using only the style
tag in your .vue
file, you can combine different approaches according to your needs and preferences.
Besides that, you can also use CSS-in-JS libraries like Vanilla Extract and Vue Styled Components, or UI libraries like Vuetify and PrimeVue, or even integrate with the popular Tailwind and Bootstrap 💚
Now, let’s go build some beautiful apps. Big hug!