Skip to content
David Graham edited this page Jun 26, 2018 · 9 revisions

In this section of the tutorial we'll discuss what's involved in structuring styles...

Install Sass and Stylus

Let's install Sass (for working with Vendors) and Stylus (for cleaner syntax in our own styles) and their respective loaders for Webpack:

$ npm install sass-loader node-sass stylus stylus-loader --save-dev 	

Stylus Extension for VSCode

If you look in your .vscode/extensions.json file we already added the extension as a recommended extension for this project. Make sure you've installed that extension for VSCode to get stylus syntax highlighting.

Load Variables and Functions to All Components

It's not well documented but you can load your Stylus variables/functions/mixins to all Vue components easily. (Because we aren't loading any actual styles here, we won't have any files unnecessarily exported in our final CSS multiple times). Update the following stylus and styl properties in the return statement here:

build/utils.js

exports.cssLoaders = function (options) {  
  // ...  

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less'),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus', { 
      preferPathResolver: 'webpack', 
      import: [
        '~@/styles/stylus/1-settings/1-settings.styl', // <-- Load these files into every stylus file.
        '~@/styles/stylus/2-tools/2-tools.styl',       //     Only variables/functions so output CSS is not increased.
      ] 
    }),
    styl: generateLoaders('stylus', { 
      preferPathResolver: 'webpack', 
      import: [
        '~@/styles/stylus/1-settings/1-settings.styl', // <-- Load these files into every stylus file.
        '~@/styles/stylus/2-tools/2-tools.styl',       //     Only variables/functions so output CSS is not increased.
      ] 
    })
  }
}

Note: Be careful to only put variables or functions here. If you put a large set of styles here you will find Webpack will take longer to build initially and on hot-reload. An alternative is setting an alias 'stylus': resolve('src/styles/stylus') and then import settings directly into the components you need them: @import '~stylus/1-settings/1-settings.styl'.

Overriding Styles in Vendor Components

Whenever you need to override some vendor styles (even if the vendor has them scoped in a component), you can just increase the specificity of your CSS by adding a document-level #app to your styles. This is easily done with Stylus. You just need to add #app to the top of the file.

Sass Folder Structure

Since we're using Stylus, we'll just have a few Sass files to handle vendor variables and imports. Sass (particularly SCSS) is very popular, so it's good to have a folder specifically for loading vendor packages who use this language. You can load them and override any variables here.

Here's a breakdown of the Sass directory structure found in src/styles/scss:

File Description
_variables.scss Add sass variables here. For new variables, use the Stylus folder instead. The underscore denotes that this file is a sub-module and would not be loaded directly at the project's entry point.
_vendor.scss Load up your vendor sass files here, in the order you want them.
main.scss Your entry point for sass files to be loaded in the order you need.

Stylus Folder Structure

Stylus is the syntax of the future. Even though this project depends on packages still using SCSS, we can go forward in our own work by using Stylus. Typically I will take the variables in _variables.scss that are important to me and create equivalents in Stylus. This is a bit redundant but it does create a nice bounded context for you. If vendor Sass variables change, your Stylus files don't need immediate updating since they don't directly depend on the Sass variables. Whenever you cross a bridge to newer/improved technology there's usually extra crossover code involved.

Here's a breakdown of the Stylus directory structure found in src/styles/stylus. We will follow ITCSS layering:

Folder Description
1-settings Used with our stylus preprocessor and contain font, colors definitions, etc. variables. We'll also automatically import this into every Vue component.
2-tools Globally used mixins and functions (we'll automatically import into every Vue component). It’s important not to output any CSS in these first 2 layers.
3-generic Reset and/or normalize styles, box-sizing definition, etc. This is the first layer which generates actual CSS.
4-elements Styling for bare HTML elements (like H1, A, etc.). These come with default styling from the browser so we can redefine them here.
5-objects Class-based selectors which define undecorated design patterns, for example media object known from OOCSS
6-components This is where we can create CSS-only components. The majority of our work for styling components will go inside the Vue component file, but some components may be CSS-only and can be added here.
7-utils Utilities and helper classes with ability to override anything which goes before in the triangle, eg. hide helper class.

Note: You may need to add a /vendor folder to some of these folders for working with vendor styles. Be careful to add them in the order you need.

Collect all your Stylus files and arranges them in the correct loading order:

src/styles/stylus/main.styl

@import '1-settings'
@import '2-tools'
@import '3-generic'
@import '4-elements'
@import '5-objects'
@import '6-components'
@import '7-utils'       

Finally, bring these files into your main project entry point:

require('./styles/scss/main.scss')
require('./styles/stylus/main.styl')

BEM Methodology

Read up on BEM. Or just read this and this.

This is BEM

CSS

/* Block component */
.btn {}

/* Element that depends upon the block */ 
.btn__price {}

/* Modifier that changes the style of the block */
.btn--orange {} 
.btn--big {}

HTML

<a class="btn btn--big btn--orange" href="http://css-tricks.com">
  <span class="btn__price">$9.99</span>
  <span class="btn__text">Subscribe</span>
</a>

Example With Stylus and Pug

Stylus

// The component (with project namespace "my-").
.my-list
    padding: 40px 0
  background-color: white
  color: black
  
  // A list variation. A dark list.	
  &--dark
    background-color: black
  color: white
  
    // A list element.
  &__item
      text-align: center
      height: 50px
      
      // A list item variation.
      &--nav
        cursor: pointer

      // A list item variation, but one that is from a general set
      // typically used by Javascript (ie. active, disabled, etc.).
      // Demonstrates that BEM is a guide, not law.  
    .my-active
      cursor: auto
      background-color: grey
    
    // Another list element (even though it's underneath .my-list__item)
    &--avatar
      border-radius: 50%

Pug

// Pug template
#app
  .my-list.my-list--dark
    .my-list__item.my-list--nav.my-active Home
    .my-list__item.my-list--nav About
    .my-list__item.my-list--nav Contact Us
      img.my-list__avatar(src='/images/phone.png')

Don't do this

CSS

.nav .nav__list-item .btn--orange {
  background-color: green;
}

Prefer classes over generic HTML elements

Take Bulma as an example, where nothing depends on the HTML element tag. This doesn't mean you can't or shouldn't use HTML elements, but frees you from that restriction. It's perfectly fine to use elements in your template or even for selectors underneath scoping BEM classes (and sometimes you have to do to access vendor components):

Stylus

.navbar
  
    &__hamburger
    float: right
    
    i
      font-size: 32px  

Use BEM with Scoped in Vue Files

To avoid collisions/confusion from vendor styles, your own global styles, or parent/children styles in your Vue components, use BEM naming along with Vue-loader's scoped attribute (or simply don't use the scoped attribute if you experience issues since BEM will work alone): &lt; style scoped &gt;. If you use BEM in both Vue components and also CSS-only components/elements, then you have full project consistency!

Without a naming methodology like BEM, you're more likely to name stuff in your Vue file that could collide with vendor CSS rules (or even your own global rules). Imagine a .btn in your Vue file's template. Who owns it? Maybe you prefix it, so you have .my-btn so you know it's yours. But is that one of your global generic button styles, or one specific to this component? So you rename it .my-foo-btn. Look familiar? That's BEM.

Clone this wiki locally