HTML & CSS - - By Hugo Giraudel

My Current CSS and Sass Styleguide

It’s never easy to find a way to write consistent, future-proof, and robust CSS. It’s taken me months, if not years, to come up with a clean way to write mine. In fact, my process is still evolving, and I expect it to change even more.

In the meantime I have accumulated enough knowledge to write an article about how I feel about writing CSS. I don’t claim that these suggestions are for everyone, and they’re certainly not perfect. But I thought it would be nice to put these into an article to share them with the community.

Naming conventions

Naming conventions are a team decision. If, like me, you are dealing with your CSS on your own, you can go nuts and use something that suits you. Personally, I go with Nicolas Gallagher’s methodology which is influenced by the well-known Yandex BEM system.

BEM stands for Block Element Modifier, a clever and clean way to name your CSS classes. Yes, I said classes, not IDs. I don’t use any IDs in my CSS and neither should you. Any. IDs are overkill and can lead to specificity issues. Of course, IDs are still very useful for JavaScript hooks and HTML anchors.

The point behind the BEM syntax is to provide context directly into the selector in order to make them easily understandable to anyone new to the project.

Here is a very basic example of BEM:

.block { }
.block--modifier { }
.block__element { }
.block__element--modifier { }

Yes, double dashes, double underscores. We’ll see why in a moment.

So, what’s up with BEM? It looks long and ugly. Well yes it is! But the fact that is is long and ugly is actually what makes it efficient.

The main idea is simple: when you build a component, you give it a name. A child element of the component will be named like this .{{ name of component }}__{{ name of child-element }}. When an element is slightly modified it will be named .{{ name of component }}--{{ name of modifier }}. Of course, a child-element can be modified as well.

So what’s up with the double dashes and underscores? Well, these allow hyphenated names like .my-component. Then you can read .my-component--disabled without a problem. Same goes for the double underscores. Worry not, you will get used to it very quickly.

Since nothing’s better than a good ol’ example, here is a step-wizard module I wrote at work using the BEM naming system (which you can find on CodePen):

.steps { }
.steps__item { }
.steps__item--first { }
.steps__item--last { }
.steps__item--active { }
.steps__item--done { }
.steps__link { }

As you can see, we deal with 3 different elements:

  • .steps which is the module wrapper
  • .steps__item which is the main element for the module since it has at least 4 modified states
  • .steps__link, another element in the module

This may look kind of verbose but I can assure you this is a real pleasure to deal with once you get used to it. It is very descriptive and less prone to mistakes and confusions.

Naming variables

There has been a whole debate over naming Sass variables, especially those involving colors. Should we name our variables like the colors they refer to or should we name them according to their purpose across the project? Basically, which one would you rather have to deal with?

$blue: #4183c4;
// Or
$primary-color: #4183c4;

This is really a matter of opinion. I go with both.

$blue: #4183c4;
$primary-color: $blue;

This way I can have a bunch of cool variables like $blue-like-the-sky, $red-like-blood, and $sexy-pink that make more sense than hexadecimal colors and meaningful variables across the project to determine which color is supposed to be used where (primary here, secondary there, and so on).

Note: Hyphens (-) and underscores (_) are treated the same way in Sass so $blue-like-the-sky and $blue_like_the_sky are both refering to the same variable.

Naming Breakpoints

If you are not already naming your media queries, you really should do so. It makes a huge difference when having to update a breakpoint or read some code that is highly dependent on screen resolution. Naming breakpoints is as easy as using this simple mixin:

@mixin breakpoint($name) {
  @if $name == "small" {
    @media (max-width: 767px) {
      @content;
    }
  }
  @else if $name == "medium" {
    @media (max-width: 1024px) {
      @content;
    }
  }
  @else if $name == "large" {
    @media (min-width: 1025px) {
      @content;
    }
  }
}

I used to pick a simple naming system like: small, medium, and large with the ability to add tiny or huge if required. This has the major benefit of being both very simple to read and device-agnostic, which is not the case with something like phone, tablet, and desktop, which don’t make much sense anymore since we have extra large phones and tablets and very small computer screens.

But! You could think of something more playful if you want to have some fun and your team is okay with it. I still go with baby-bear, mama-bear, and papa-bear from Chris Coyier on my own site, but why not something like r2d2, c3p0 and chewbacca? More cool examples like those are found in Chris’s article.

Tabs vs. Spaces

This is the kind of topic that leads to nuclear war, so I will just give you my opinion of the situation – feel free to think otherwise. I used to use only tabs but I ended up switching to spaces. Two spaces.

I am not sure there are many reasons for this, except that I feel like it suits me best. It has its pros and cons. Among the pros, it avoids having lines starting in the middle of the screen due to extra nesting.

Also, basic editors do not handle tabs very well. Think of textarea elements for instance. They let you insert spaces, but not tabs (unless you copy and paste from elsewhere, which is not ideal). Spaces are supported everywhere. Hence my choice for the former.

CSS Rule Sets

I like when things are clean. All my CSS rule sets are written the same way and they follow strict (yet standard) conventions. Here is an example:

.selector,
.other-selector {
  color: black;
  padding: .5em;
  font-size: 1.2em;
}

Well, this is nothing more than the basics, which includes:

  • One selector per line
  • One space before the opening curly brace
  • A line break after the opening curly brace
  • A consistent indent
  • No space before the colon
  • A space after the colon
  • No space before the semi-colon
  • A line break after the semi-colon
  • No space before the closing curly brace
  • A line break and an empty line after the closing curly brace

Some side notes:

  • I tend to get rid of the padding 0 when dealing with decimal numbers between 0 and 1, making the number start with a .;
  • I try to make colors as short as possible, following this order: keyword > small hex triplet > hex triplet > rgb/hsl > rgba/hsla,
  • I avoid magic numbers at all cost (numbers that are not round or do not make sense by themselves),
  • I don’t use any px values (only em or even rem if browser support allows it) except when it is one (1px); pixels are not scalable, that simply sucks.

Sass Stuff

What I’ve just run through are pretty standard CSS guidelines. Now for some Sass-specific stuff.

.element {
  $scoped-variable: whatever;
  @extend .other-element;
  @include mixin($argument);
  property: value;

  &:pseudo {
    /* styles here */
  }

  .nested {
    /* styles here */
  }

  @include breakpoint($size) {
    /* styles here */
  }
}

The different items in a Sass rule set go in the following order:

  1. Scoped variables
  2. Selector extensions with @extend
  3. Mixin inclusions with @include with the exception of media-query stuff
  4. Regular property: value pairs
  5. Pseudo-class/element nesting with & after an empty line
  6. Regular selector nesting after an empty line
  7. Media query stuff (with or without a mixin)

Taking our step-wizard example from earlier, here is what a real-life example in the above order would look like:

.step__item {
  counter-increment: steps; /* 1 */
  background: $step-background-color;
  float: left;
  padding: $step-baseline 0;
  position: relative;
  border-top: $step-border;
  border-bottom: $step-border;
  white-space: nowrap;
 
  &:after {
    @include size($step-arrow-size);
    @include absolute(top .3em left 100%);
    @include transform(rotate(45deg));
    content: '';
    z-index: 2;
    background: inherit; 
    border-right: $step-border;
    border-top: $step-border;
    margin-left: -$step-arrow-size/2;
  }

  &[disabled] {
    cursor: not-allowed;
  }

  @media (max-width: 767px) {
    width: 100% !important;
    border-left: $step-border;
    border-right: $step-border;
    padding: $step-baseline*2 0;

    &:after {
      content: none;
    }
  }
}

Nesting in Sass

Nesting has to be one of the most controversial features of Sass, or any other CSS preprocessor for that matter. Nesting is both useful – to avoid repeating the same selector again and again – and dangerous, because it can produce unexpected CSS. Only use nesting when it’s what you mean, not just because it is convenient. If you mean, .a .b, then you can write .a { .b { } }. If .b happens to be inside of .a but is enough by itself, don’t nest it.

Inception meme: We need to go deeper

I bet you’ve seen the movie Inception. Great movie, right? Well, you know how the characters can’t go any deeper than a dream within a dream within a dream, otherwise they end up in ‘limbo’? Well Sass nesting is the same: Don’t nest deeper than 3 levels. Ever. If you have to, it means either your markup or your CSS is poorly written. Either way, you might end up in ‘limbo’ and, as we saw in the movie, it ain’t pretty down there.

“Don’t nest deeper than 3 levels.”
Cobb

Comments

When you work with Sass, you have two different ways to write your comments: the good ol’ /* */ you all know well, and the single line comment // that doesn’t exist in plain CSS (although you can get around that). The difference between these is that the single-line comments will not be compiled in the resulting stylesheet, ever. This is why single-line comments are referred to as “transparent comments” or “invisible comments”. Meanwhile, the regular CSS comments will be in the compiled CSS if you’re not compiling in compressed mode (you should be though).

As far as I am concerned, I comment a lot. CSS is a language full of tricks, hacks and dirty little secrets. I feel like I need to comment if I want someone (even me) to understand the code months later, even things that seem obvious at the time of writing the code.

/**
 * Purpose of the selector or the rule set
 * 1. Hardware acceleration hack (http://davidwalsh.name/translate3d)
 * 2. Fallback for unsupported rgba
 */
.selector {
  @include transform(translate3d(0, 0, 0)); /* 1 */
  background: black; /* 2 */
  background: rgba(0, 0, 0, .5);
}

I start with a small description of the rule set, then I number tiny details that are worth an explation. The numbers are then matched with numbered comments at the end of CSS rules (e.g. /* 1 */).

In the end I don’t use single-line comments much, expect in Compass extensions (e.g. SassyJSON). I started doing so when I realized regular CSS comments from my first Compass extension SassyLists were dumped to the CSS output on Sassmeister.

Quasi-qualified selectors with comments

I believe the idea of using comments to avoid qualifying selectors comes from Harry Roberts.

Qualified selectors are bad for many of the reasons described in Harry’s post. The most important ones being: They are over-specified unnecessarily, they break the cascade, and they are not reusable.

That being said, being able to say “this class should only be applied to this type of element” can be useful. Let’s say at some point you use CSS transforms and create a fallback for unsupported browsers, using Modernizr to detect support. Here is what your code might look like:

.no-csstransforms .selector {
  /* Styles here */
}

Now what if you want to explicitly indicate that the no-csstransforms class is meant to be on the html element and not anywhere else? Doing html.no-csstransforms would make your selector over-qualified and is not a good idea. You could use a regular comment above the rule set to make a note, as we’ve seen in the previous section. Or you could try quasi-qualified selectors:

/*html*/.no-csstransforms .selector {
  /* styles here */
}

There you go: the class is now explicitly meant to be bound to the html element without altering you selector at all or breaking anything. This technique may look very odd at first but you can get used to it pretty quickly.

Final thoughts

As already mentioned, you might not feel comfortable using all these rules. And I don’t expect you to. What’s important is that you and your team are able to read each other’s code and update it easily.

Probably the best thing you can do with this post is take these suggestions and make them your own, adding your own flavour to them to improve your own workflow.

If you have developed your own methods for any of these areas of Sass and CSS, we’d love to hear them in the comments.

Sponsors
Login or Create Account to Comment
Login Create Account