Skip to content

The Vue.js 101 - A little more about styles

#javascript #learning #vuejs2 #CSS #framework #code
The Vue.js 101 series is about things I wish someone had told me about when I first started working with the framework. Have you ever wondered how can you limit or extend the capabilities of the styles you define on each component? And how do you even structure your styles in a Vue.js application? Let's look into it!

Each component gets its own CSS style. But what does this really mean? It is an easy mistake to think that this way, styles described in their individual components can’t conflict with each other. The true purpose of the cutting up the CSS into smaller chunks is that Vue won’t have to load a huge style sheet at once (which in turn would increase page load times, especially in larger projects).

Scoped and modular style

By default, component styles are not “namespaced”.

However, vue-loader does exactly what you would expect component specific styles to do, and that is to create scoped and modular styles by generating component-specific class names. Scoped style means that the styles also apply to any child-component, while CSS modules will truly only apply to the component they are defined on.

It’s just a matter of configuration. If you are using plain Vue.js, the configuration happens in your webpack.config.js/babel.config.js file, as vue-loader is actually a webpack loader. You will find the array of rules under modules, like so:

rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
<style scoped>
.modular-class-example {
  color: teal;
}
</style>

Modular CSS will require a little more tweaking:

{
        test: /\.css$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              localIdentName: '[local]_[hash:base64:8]' // to customize generated class names
            }
          }
        ]
      }
<style module>
.modular-class-example {
  color: teal;
}
</style>

While scoped styles will be inherited by child components, CSS modules will only be accessible by the components they are specified in, and will be registered under a computed property called $style, and you can use them through dynamic class binding:

<p :class="$stlye.modular-class-example">some colored text</p>

Preprocessor support

If you are like me and you enjoy the extra utility that CSS-preprocessors provide, you're going to be pleased to hear that Vue.js supports preprocessors. SASS, SCSS, LESS and many others, make your pick!

npm install -D sass-loader node-sass

After installing the necessary packages, it only needs a little configuration in your webpack.config:

rules: [
    {
      test: /\.scss$/,
      use: [
        'vue-style-loader',
        'css-loader',
        'sass-loader'
      ]
    }
  ]
<style lang="scss">
.style {
  font-size: 1em;

  .nested-style {
    color: purple;
  }
}
</style>

Even better, this also works for typescript and pug!

npm install -D typescript ts-loader

rules: [
    {
      test: /\.ts$/,
      loader: 'ts-loader',
      options: { appendTsSuffixTo: [/\.vue$/] }
    }
  ]

npm install -D pug pug-plain-loader

rules: [
  {
    test: /\.pug$/,
    loader: 'pug-plain-loader'
  }
]
<template lang="pug">
  div
    p html has never been easier!
</template>

Structuring your styles

Splitting your stylesheets up through the components might seem like a pattern that immediately solves all your CSS problems, and if you're building an SPA it might be true. But large frontend applications need some more thought put into your CSS structuring, otherwise you're going to quickly find yourself in the depths of hell full of !important tags.

So what do I do to keep my sanity and design my CSS so I won't have to constantly override every style I defined?

First of all, there's a good reason I mentioned preprocessors before this section, because my go-to prep is SCSS (because I like to torture myself and waste all the time I save with pug syntax on writing curly braces and semicolons in SCSS). It's important to note that I don't put all of my styles inside the components. I keep my styles in the assets directory, and these stylesheets can be categorized as:

  • variables
  • global styles
  • mixins
  • overrides

Variables

This defines the character of my application. I like to pre-define my color palette, my fonts, my spacings and use these variables everywhere. The benefit of this approach is that you can always tweak your colors or fonts very quickly and easily because you don't have to hunt down every single occurrence of them. Small, quick changes can be crucial in production because n case you ever want to split-test your design choices, working with variables will save you a lot of time in the long run.

// COLORS

$color-primary: #313035;
$color-secondary: #D1D3D4;
$color-base-gray-light: #A7A9AC;
$color-base-gray-medium: darken($color-base-gray-light, 20%);

$base-font-family: 'Roboto', sans-serif;
$cursive-font-family: 'Play', sans-serif;

Global styles

This is the groundwork of the website. Think about html tags that you almost never want to override, or tags and elements you know you would want to use across multiple components. Global styles are the way to go for that. The best part is that you can use your pre-defined variables to reference the colors, fonts and spacings:

body {
  background-color: $color-background;
  box-sizing: border-box;
  color: $font-color-dark;
  font-family: $base-font-family;
}

Mixins

Mixins are reusable bits of style that you can include anywhere in your stylesheets. They help keep your CSS DRY by making common repeating style elements easily callable with just one line of code.

I often use mixins for:

  • media queries
  • color themes
  • positioning
  • CSS animation
  • image filters
// BREAKPOINTS

$breakpoints: (
  "sm": 767px,
  "md": 992px,
  "lg": 1199px
) !default;

@mixin size-below($breakpoint) {
  @if map-has-key($breakpoints, $breakpoint) {
    @media (max-width: map-get($breakpoints, $breakpoint)) {
      @content;
    }
  }

  @else {
    @warn "No size reference for `#{$breakpoint}`"
  }
};

In action:

.container {
  width: $content-width;

  @include size-below(sm) {
    width: 95%;
  }
}

Overrides

I don't often see this type of CSS separated but I also like to keep them in their own files so I can always keep track of them. I utilize overrides when I a CSS library such as Bootstrap, but I would like to alter their default styles to fit my concept, so I can keep using the pre-defined classes and variables without having to worry about all the individual components I might have altered them separately.

For example, you can override bootstrap's own color theme if you want to go for a less saturated look:

$theme-colors: (
  primary: #5292B3,
  secondary: #B86971,
  success: #86d5ab,
  info: #86d4d5,
  warning: #ecd18f,
  danger: #dd5b5b,
  light: #e1e1e1,
  dark: #222425
);

So what happens inside the components?

Breaking your application up into the perfect-sized components takes practice and experience, but you will know you are doing a good job when your components are flexible enough in a way that you can reuse them often enough without you having to create a unique component for each individual html tag.

Your style definitions will also reflect this, you should always keep your styles strictly tied to the template. Using SCSS or SASS helps a lot because the style nesting often lets you write components styles without having to scope or modularize the style, and also not having to worry that it may conflict with another style elsewhere.

I hope you learned something new about CSS usage in Vue.js applications. Happy coding :)

End of article