July 22, 2020

Making Use of Scoped Styles in Vue

Cover image

Before Vue, we used to scope a lot of our styling by a class on <body> or with sufficient nesting levels. For any large enough project we’d end up with a pretty large body of SCSS modules and it was always an effort to locate the style you needed to modify or find the right place to add some new bits.

body.account {
  input {
    border: 1px solid $input-border-color;
	border-radius: 4px;
	padding: 0.5rem 1rem;
  }
}

As we’ve been doing larger Vue applications, we’ve relied less and less on a large SCSS module system and have been putting relevant styles directly inside single file components.

<template>
  <input class="my-custom-input">
  </input>
</template>

<script>
  ...
</script>

<style type="scss" scoped>
  @import '../config';

  input {
    border: 1px solid $input-border-color;
	border-radius: 4px;
	padding: 0.5rem 1rem;
  }
</style>

By using the scoped attribute we don’t have to rely on deeply nested blocks and that has provided a lot more freedom to update style quickly without a lot of regression testing. Furthermore, we rarely have to go searching for where to modify some bit of style on a component. It’s all right there.

There are invariably some common style elements that we either put into App.vue in an unscoped block or their own modules. Often is the case though, if we can break out a module for some style, for say buttons, we can just as easily make a <CustomButton> component to encapsulate the style AND the markup and function.

We still use a top level file to configure values into variables to make sure we don’t end up with 57 different grays. We can make these variables globally available with a small addition to our webpack config.

Here is an example of injecting our variables config into a Vue CLI project's webpack config:

// vue.config.js
const path = require('path');

module.exports = {
  pluginOptions: {
    'style-resources-loader': {
      preProcessor: 'scss',
      patterns: [path.resolve(__dirname, './src/_variables.scss')],
    },
  },
  configureWebpack: {
    resolve: {
      symlinks: false,
    },
  },
};

Also, at times we might need to manipulate the style of a component from a parent depending on the context. Since we are relying so much now on scoped that can be tricky. That's where using v-deep lets you pierce that wall.

<template>
  <div class="my-awesome-component">
    <ChildComponent />
  </div>
</template>

<style lang="scss" scoped>
  .my-awesome-component {
    ::v-deep a {
      display: none;
    }
  }
</style>

Finally, there might be times where you need to define both scoped and unscoped style in a component. It’s perfectly fine to define multiple <style> blocks in a single file component for each purpose.

<template>
  <div class="my-awesome-component">
    <ChildComponent />
  </div>
</template>

<style lang="scss" scoped>
  .my-awesome-component {
    ::v-deep a {
      display: none;
    }
  }
</style>

<style lang="scss">
  // some cool style you want applied globally but makes sense to have here in
  // this component file
</style>

Keeping styles close to the markup and function of each component means a lot less time searching through files to find the right one to edit. Also, using scoped ends the need worry about how the rest of the project might be impacted by the style rules you are writing. This combines to free you up to just focus on the things that matter.

This post was originally published on the Eldarion Blog.