Ler em pt-BR

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

Guy with style


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>

RouterLink showing color from class txt-tomato declared in global CSS

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 the scoped 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>

RouterLink showing color from class  raw `example` endraw  declared in scoped CSS and h1 in a different component ignoring the color from the same class

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>

RouterLink and h1 show the same colors because of :deep selector

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:

RouterLink and h1 in different components with same class and different results

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>

RouterLink shows yellow color from external CSS Module instead

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:

Compiled Vue code in the browser devtools

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!