Vue.js 3 Component Styling & CSS Tutorial

In this Vue tutorial we learn how to style components locally.

We cover global and local scopes as well as unscoped styles and how they affect other components.

Finally we cover how to add standalone or external CSS files to your application.

Lesson Project

If you want to follow along with the examples, you will need an app generated by the Vue CLI as well as the following extra components.

  • src/components/ChildStyle.vue

The project should look similar to the following.

Example: project
project-folder/
├── src/
|   ├── components/
|   |   ├── ChildStyle.vue
|   └── App.vue

We want to nest ChildStyle inside the root App component.

Example: src/App.vue
<template>
  <h2>Parent Component</h2>
  <hr>
  <child-style />
</template>

<script>
import ChildStyle from './components/ChildStyle.vue'

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

In ChildStyle , we’ll have a heading with some identifying text.

Example: src/components/ChildStyle.vue
<template>
  <h2>Child Component</h2>
</template>

Scoped styles: Global and Local

By default, CSS in Vue is global. This means that the CSS in one component can affect any other components in our application.

For example, if we define a style for the heading in our ChildStyle component, it will also affect the heading in the root App component.

To demonstrate, let’s add a CSS rule for the h2 element in the style block that sets the text color to blue.

Example: src/components/ChildStyle.vue
<template>
  <h2>Child Component</h2>
</template>

<style>
h2 { color: blue }
</style>

If we save the file and take a look in the browser, we’ll see that both heading elements are blue.

Typically we want headings to be the same color, but what if we want this specific component to stand out from the rest. In that case, we want the root App component to have the default color and the ChildStyle component is only allowed to change its own color.

Vue allows us to scope the styles in the style block to the current component only. To do this we attach the scoped attribute to its style block.

Syntax: scoped
<style scoped>
  /* CSS styling */
</style>

The scoped attribute will tell Vue that we want everything in this style block to be of a local scope. This means it will only affect the elements in the current file.

To demonstrate, let’s add the scoped attribute to the ChildStyle component’s style block.

Example: src/components/ChildStyle.vue
<template>
  <h2>Child Component</h2>
</template>

<style scoped>
h2 { color: blue }
</style>

If we save the file and take a look in the browser, we see that the root App component’s heading is the default black color, and ChildStyle ’s heading is blue.

How the cascade affects unscoped component styles

If we have multiple child components whose style blocks aren’t scoped, the styles in the component that’s imported last will override any other styles.

To demonstrate, let’s create another component called SiblingStyle.vue and nest it in the root App component.

Example: src/App.vue
<template>
  <h2>Parent Component</h2>
  <hr>
  <child-style />
  <hr>
  <sibling-style />
</template>

<script>
import ChildStyle from './components/ChildStyle.vue'
import SiblingStyle from './components/SiblingStyle.vue'

export default {
  components: {
    ChildStyle,
    SiblingStyle
  }
}
</script>

The SiblingStyle component will have the same content as ChildStyle except the style will not be scoped and the heading color is red.

Example: src/components/SiblingStyle.vue
<template>
  <h2>Sibling Component</h2>
</template>

<style>
h2 { color: red }
</style>

Next, we need to remove the scoped attribute from ChildStyle to make the demonstration work.

Example: src/components/ChildStyle.vue
<template>
  <h2>Child Component</h2>
</template>

<style>
h2 { color: blue }
</style>

If we save the files and take a look in the browser, all the component headings are colored red.

CSS works in a cascade, which means that styles that are defined lower in the file will override any of the same styles above it. Because SiblingStyle is imported below ChildStyle , its styling is defined lower in the CSS.

If we swap the two imports so that ChildStyle is last, its styling will override the SiblingStyle and make the headings blue.

Example: src/App.vue
<template>
  <h2>Parent Component</h2>
  <hr>
  <child-style />
  <hr>
  <sibling-style />
</template>

<script>
  import SiblingStyle from './components/SiblingStyle.vue'
  import ChildStyle from './components/ChildStyle.vue'

  export default {
    components: {
      ChildStyle,
      SiblingStyle
    }
  }
</script>

Unscoped styles from the parent takes precedence

If the style from a parent is the same as the style of its child, and both are unscoped, the parent style will override it. In our application, we have the root App component as a parent to the other two components.

To demonstrate, let’s add a style block to the root App component that changes the color of the heading to green.

Example: src/App.vue
<template>
  <h2>Parent Component</h2>
  <hr>
  <child-style />
  <hr>
  <sibling-style />
</template>

<script>
import SiblingStyle from './components/SiblingStyle.vue'
import ChildStyle from './components/ChildStyle.vue'

export default {
  components: {
    ChildStyle,
    SiblingStyle
  }
}
</script>

<style>
h2 { color: green }
</style>

If we save and take a look at the browser, all the headings will be the green we defined in the parent.

How to add standalone/external CSS files to Vue

Typically, we style our apps with frameworks like Tailwind or per-component styling. However, if we have a standalone or external CSS file that we want to add to the app, all we need to do is import it into the main.js file.

As an example, let’s create a /src/main.css file with a rule that changes the body background to black.

Example: src/main.css
body { background: black }

As mentioned, all we need to do is import it into the main.js file and it will work.

Example: src/main.js
import { createApp   } from 'vue'
import App from './App.vue'
// import external CSS
import './main.css'

const app = createApp(App)
app.mount('#app')

If we save and take a look in the browser, the background will be black.

An imported CSS file will act as an unscoped style from the parent and will take precedence.

As an example, let’s add a rule to the main.css file that changes the heading color to white.

Example: src/main.css
body { background: black }
h2 { color: white }

In the browser the text color will be white, confirming that the CSS file takes precedence over other styles.