Vue

7 Quick Tips about Vue Styles You (Might) Didn’t Know

Single file components consist of three distinct entities: template, script and styles. All of them are important but the latter is often…

Fotis Adamakis
Fotis Adamakis
Senior Software Engineer / Technical Writer
4 min read
September 24, 2023

7 Quick Tips about Vue Styles You (Might) Didn’t Know

7 Quick Tips about Vue Styles You (Might) Didn’t Know

Single file components consist of three distinct entities: template, script and styles. All of them are important but the latter is often being neglected even though it can become complex and often cause frustration and bugs. A better understanding can improve code reviews and decrease debugging time.

Here are 7 tips to help you with that:

  1. Styling Scoped And Slotted Content
  2. Scoped Selector Performance
  3. Global Styles
  4. Javascript Variables in Styles
  5. CSS Modules
  6. Variables in CSS Vs SCSS
  7. SCSS include Vs extend

1. Styling Scoped And Slotted Content

Scoping the styles to affect only the current component is a great tactic to prevent component coupling and unexpected side effects.

It is achieved by adding the scoped attribute to transform the following:

<template>
  <div class="title">Hello world!</div>
</template>

<style scoped>
.title {
  font-size: 24px;
}
</style>

To:

<template>
  <div class="title" data-v-7ba5bd90>Hello world!</div>
</template>

<style scoped>
.title[data-v-7ba5bd90] {
  font-size: 24px;
}
</style>

[Try it](https://play.vuejs.org/#eNp9kcFOwzAQRH/F+FxSiXIKoRKgSsABEHD0JUq2wcWxLe8mDVT5d9aOWnqoerNn5lmz65288z7rO5C5LAhab0qCpbJCFLXuRWVKxFslSZMBJZePYIwTWxdMfVHMOcHRYn7E8RXpx4DAynmoWckSK3bxzbWzdIn6F3Jxde2HG2XHyCciwXImiUm71k22QWe5VQKVrFzrtYHw6kk7i0rm05PRK7nU9jlpFDqY7fXqC6rvE/oGh6gp+RYAIfQ82cGjMjRAk736eIGBzwezdXUX93DGfAd0posdp9h9Z2uufZRLbZ9a7wJp23ziaiCwuB8qFo3JMeWV5K95ODP6f91Ftkgc71SOfyznnDs=)

If you want a style to affect the child components you can use the deep selector.

<style scoped>
  .a :deep(.b) {
    /\* ... \*/
  }
</style>

Which compiles to:

.a[data-v-7ba5bd90] .b {
  /\* ... \*/
}

And the same goes for content inside a slot using the slotted selector.

<style scoped>
  :slotted(div) {
    color: red;
  }
</style>

2. Scoped Selector Performance

Scoped styles do not eliminate the need for classes. Due to the way [CSS selectors work](https://blogs.windows.com/msedgedev/2023/01/17/the-truth-about-css-selector-performance/), p { color: red } will be many times slower when scoped. If you [use a class instead](https://v2.vuejs.org/v2/style-guide/#Element-selectors-with-scoped-use-with-caution) that performance hit is negligible.

<!-- DO -->
<template>
  <h1 class="title">Hello world!</h1>
</template>

<style scoped>
.title {
  font-size: 24px;
}
</style>
<!-- DONT -->
<template>
  <h1>Hello world!</h1>
</template>

<style scoped>
h1 {
  font-size: 24px;
}
</style>

3. Global Style

A style that affects the whole application is probably not a good idea. In case you want to do it anyway you can either mix a scoped with an unscoped style or use the :global pseudoselector

<style scoped>
  :global(.red) {
    color: red;
  }
</style>

4. Javascript Variables in Styles

Since Vue version 3.2 it is possible to use v-bind inside the style tag. This can lead to some interesting use cases like implementing a color picker with a few lines of code.

<template>
  <h1 class="text">Hello World!</h1>
  <input type="color" v-model="color" />
</template>

<script setup>
import { ref } from "vue";
const color = ref("");
</script>

<style>
.text {
  color: v-bind(color);
}
</style>

Try it

A more advanced use case would be making the icon sizes of [a reusable app icon component](/integrating-an-icon-library-to-a-vue-application-b342fee12fae) dynamic.

<template>
  <p>
    <input type="radio" v-model="size" :value="sizes.s" />S
    <input type="radio" v-model="size" :value="sizes.m" />M
    <input type="radio" v-model="size" :value="sizes.l" />L
    <input type="radio" v-model="size" :value="sizes.xl" />XL
  </p>
  <div class="icon" />
</template>

<script setup lang="ts">
import { ref } from "vue";

enum sizes {
  s = 8,
  m = 16,
  l = 32,
  xl = 64,
}

const size = ref(sizes.xl);
</script>

<style>
.icon {
  width: v-bind(size + "px");
  height: v-bind(size + "px");

  background: #cecece;
}
</style>

[Try it](https://play.vuejs.org/#eNqlUk1v1DAQ/Ssjc1gq2ix0UYVCWglQDyC+RDlw8MVNZrNuHdvyR5qy2v/OjFNKD7SXVQ4Zz7x5fvM8W/HO+2rMKGrRJBy8UQnPpAVofPlRoK3PCdKtx1Mpguq0kwLGo8F1aCgT9W+kRD0qkxnB51hFKc4u9iEYiODLPgSGCD7vQzAxw69C0SxnN5pOj9AaFSOhdOssdS2p0iwfeEfH2AbtE0RM2YNRtid4Ykuk1YN3IcEWAq5hB+vgBljQAyy4EW0eoNwOW74vwim8OeRooOjVSQkNhavjEk4cn7yWdsfdpCfSpdROWaJ//neOA1Y4a7rTl24NS614hvmqG92lTU2mXGrblU54AQs/LQ7ecnmDut+kGh4BMORStdd9cNl2NTxrkb/SmoKyUSftbA3KGHhZHUdAFfHI5VSkk7giSBySS6RorfvqKjpLS1m0SdG6wWuD4ZtnHnKynlVzjTjdzaeSSyFjMab0bLC9/k/+Kk6ck+J7wIhhpKe/ryUVekxz+fziK04U3xdpXbIh9BPFHxidyaxxhr0nN0j2A1xR+7Esgbb9z3g+JSR77oZioYzcFbwUtBgfnhj9n9xVtSp95KfY/QFkkUNk)

5. CSS Modules

CSS modules are supported out of the box simply by adding the module attribute in the style tag. The classes are automatically exposed in the template with the $style variable.

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

CSS modules specification

6. Variables in CSS Vs SCSS

SCSS variables were a big revolution in the way we write CSS. Before preprocessors using variables was just not possible. Since then CSS caught-up and CSS variables are now supported in all major browsers. They offer everything that SCSS variables can do and additionally provide easier theming capabilities which makes them a clear winner in this debate.

[More info about CSS vs SCSS variables](https://codyhouse.co/blog/post/css-custom-properties-vs-sass-variables)

7. SCSS include Vs extend

These two SCSS helpers can often cause confusion since they both can be used to reduce SCSS code repetition. There are some subtle differences in the CSS output that you should be aware of.

The @include helper is used to include the code written in a mixin block.

<template>
  <p class="error-text">Hello World!</p>
  <p class="error-notification">Hello World!</p>
</template>

<style lang="scss">
@mixin error {
  color: red;
}

.error-text {
  @include error;
  font-size: 24px;
}

.error-notification {
  @include error;
  border: 1px solid red;
  padding: 8px;
}
</style>

The produced CSS will have the code duplicated as many times as needed

.error-text {
  color: red;
  font-size: 24px;
}

.error-notification {
  color: red;
  border: 1px solid red;
  padding: 8px;
}

The error mixin here is kept to only one rule but normally more complex mixins will exist in a real world application.

On the other hand, @extend is more useful when the elements are almost identical.

<template>
  <p class="error-text">Hello World!</p>
  <p class="error-notification">Hello World!</p>
</template>

<style lang="scss">
%error {
  color: red;
}

.error-text {
  @extend %error;
  font-size: 24px;
}

.error-notification {
  @extend %error;
  border: 1px solid red;
  padding: 8px;
}
</style>

The produced code is:

.error-notification, .error-text {
  color: red;
}

.error-text {
  font-size: 24px;
}

.error-notification {
  border: 1px solid red;
  padding: 8px;
}

The general rule here is to pick extend unless you want to use a parameter with the mixin where onlyinclude will work.

Do you have any useful tips? Please share them in the comments below.

Fotis Adamakis

Fotis Adamakis

Senior Software Engineer / Technical Writer

Experienced software engineer writing about front end architecture, accessibility, system design, and developer productivity. Lessons from building and maintaining large-scale frontend applications, with a focus on practical patterns that make codebases easier to understand, scale, and evolve.

Barcelona, Spain