David Clark develops the web

An Auto-Enforceable SCSS Styleguide Powered by SCSS-Lint

Automated Enforcement Matters, and SCSS-Lint is Excellent

(If you don’t really want to read, just look at SCSS-Lint and seriously consider using it.)

There seems to be universal agreement that consistent, predictable code style is important, in any language (at least if more than one fallible human is working on the project for more than one week); and that thorough code styleguides, which enable that consistency and predictability, are well worth writing, using, and sharing.

In the world of stylesheet-code (CSS and its relatives), plenty of individuals and organizations have published their approaches, with suggested guidelines spanning the gamut of from minute finetuning (whether there should be a leading zero in 0.5) to big-picture architecture (how modules should be named and organized). I’ve been trying to keep track of them in a Scalable CSS Reading List: take a look.

So I don’t think I have to advocate for the sharing of guidelines, generally. But I do think there could be more advocacy for the sharing of automatable guidelines. In that area, the CSS-cluster is far behind other languages.

There’s a vital distinction between discussions of best practices and the actual codification of clear, specific, concrete conventions and standards that will constitute, for some group of developers, enforced expectations. All the discussions are valuable, of course; but it’s easy to forget that they must be realized in day-to-day practice, in keyboard-tapping and mouse-clicking and glazed-eyeball-flickering. The “good ideas” should aspire to become “the way things are done”. That’s why I think that among the most important properties of any guideline, whatever its scale or focus, is if and how it will be enforced.

Sometimes, for some guidelines, manual code review with human senses and human brains is the only viable means of enforcement. Machine minds probably can’t determine meaningful rule categories, when to use utility classes, whether @mixins and @extends are helpful or harmful in a given situation — and so on.

But sometimes automation will work — maybe all on its own, or maybe as a first step that eases the burden on human brains, pointing out suspicious characters that deserve a second look. (… thank god I’m just talking about code and not law enforcement … machines are so much easier … I swear I’m not authoritarian …) And those times are the best times. Just trying to make a guideline auto-enforceable will probably improve it, since you’ll have to shake off all vagueries, honing what might be loose recommendations into well-defined and delineated, observable patterns.

Here’s a sentence for you: Any styleguide will become more valuable as more of its enforcement becomes more automated.

All that’s to say that the world of stylesheet-code would benefit from more and better tools for code style enforcement. Currently, SCSS-Lint stands alone, in my opinion. Thankfully, SCSS-Lint is excellent. Full-featured, configurable, exceptionally well-maintained. One of the major reasons I stick with SCSS is because of SCSS-Lint. There should be more tools like it. (I’m not fully warmed up yet to CSSComb — maybe I need to give it some more attention — and that seems to be just about the only other option right now (?).)

If you write SCSS, give SCSS-Lint some serious thought. Furrow your brow. Work it into your build system. Plug it in to your text editor. Talk with your family about it over the holidays. If you do not write SCSS, look into SCSS-Lint anyway, and if you have some extra energy maybe do one of the following: (1) convince me that CSSComb is as good as SCSS-Lint (or make it as good); or (2) make or contribute to a LESS or PostCSS/Rework linter that might one day match it (like maybe this one?).

Enough jabber. I share this little guide below for a few reasons:

  • As an introduction to the powers of SCSS-Lint, for those who don’t yet use it.
  • As an example of the auto-enforceable section that should be part of every real-world stylesheet-code styleguide. (Note that this does not aspire to be a complete styleguide, like Harry Robert’s cssguidlin.es: it’s just the auto-enforceable part. Note that.)
  • Just to share.

My SCSS-Lint-Powered SCSS Styleguide

Please keep in mind that this is just my styleguide, not The Styleguide, and SCSS-Lint is completely configurable to fit your own weird, sometimes misguided preferences.

If any terminology below is unfamiliar, search it out in the CSS 2 spec or the Sass docs. (I’m trying to use these words correctly: please send a tweet if I’m mistaken. Educate me.)

After each guideline I’ve included the name(s) of the SCSS-Lint linter(s) that will enforce it. And I’ve posted the configuration file that would make SCSS-Lint enforce all of this, so you can inspect or copy-paste it.

Last point: I am not going to spend a lot of time below justifying each guideline. A few are really just personal preference, but for most I do have some rational justification. But there are enough words on this page already. (Also, the SCSS-Lint documentation offers a little justification for each linter, which I usually, though not always, agree with.) And when I do justify, I may be facetiously dogmatic. If you think I should take the time to further explain something, send a tweet.

Whitespace and Punctuation

» Indent using 2 spaces. Or tabs. Whatever. Fight about it. [Indentation]

» End each file with an empty newline. After all, you’re not an animal, are you? [FinalNewline]

» Include an empty line between all statements — i.e. rule sets, at-rules, and Sass directives. Even the nested ones. Spaces help people, and people are important. (Variable definitions are like properties and don’t need to be spaced.) [EmptyLineBetweenBlocks]

» Include spaces after commas and colons, not before. As in written English. [SpaceAfterComma, SpaceAfterPropertyColon, SpaceAfterPropertyName]

» Do not include spaces between parentheses and parenthesized. As in written English. [SpaceBetweenParens]

» Use single quotation marks. That way you don’t have to push shift, everyone’s least favorite key. [StringQuotes]

» End every declaration with a semicolon, with no whitespace in front of it. (Did you even think about putting a space in front of it? What is wrong with you?) [TrailingSemicolon]

Selectors and Names

All of the guidelines in this section reflect my preference for class selectors and class selectors only (except in highly unusual and probably unhappy circumstances), and keeping the hierarchy as flat as possible.

Excuse me: I say “my preference” but I mean “God’s law”.

» Never use ID selectors. Find another way. (No, this is not “throwing out the baby with the bath water”. This is “not pooping in the bath”.) [IdSelector]

» Do not qualify type selectors. Better yet, don’t use them at all, if possible: just use classes. [QualifyingElement]

// bad
a[href='horse'] { ... }

// maybe fine
[href='horse'] { ... }

// good
.horse { ... }

» Provide lowercase, hyphen-delimited names for Sass variables and directives. That way they fit in with standard CSS keywords and property names. [NameFormat]

» Do not chain more than three simple selectors. In almost every case, one simple selector is best (that is, no chaining, flat specificity, world peace). Some situations call for two. But let’s just draw the line at three. Find another way. [SelectorDepth]

// good
.horse { ... }

// often useful
.horse + .horse { ... }

// suspicious
.horse > .donkey { ... }

// uh ...
.horse > .donkey + .mule { ... }

// call the police
ul li a span { ... }

» Include a line break between each selector in a group. [SingleLinePerSelector]

// bad
.horse, .donkey, .mule { ... }

// good
.horse,
.donkey,
.mule { ... }

» Include a single space between selectors and the curly braces that begin declaration blocks. [SpaceBeforeBrace]

» Use a consistent selector naming convention. This is probably the least globally portable guideline in my guide, because it’s hard to achieve consensus on naming conventions. But maybe you’ll like mine? I try to be strict (not because I’m a fascist but because I’ve seen strange horrors in stylesheets).

Here’s what I do: Class names should match the following regular expression:

/^(?:u|is|has)\-[a-z][a-zA-Z0-9]*$|^(?!u|is|has)[a-zA-Z][a-zA-Z0-9]*(?:\-[a-z][a-zA-Z0-9]*)?(?:\-\-[a-z][a-zA-Z0-9]*)?$/

This regular expression enforces the SUIT CSS naming convention (but without requiring the initial capital letter on a component). The possibilities can be summed up like this:

.u-utilityClass {}
.is-stateClass {}
.has-stateClass {}
// And maybe I'll add other verbs for state classes, as needed
.ComponentName {}
.ComponentName-descendentName {}
.ComponentName--modifierName {}
.ComponentName-descendentName--modifierName {}
// All of the above could also use `componentName`, without the initial capital

(All non-class-name selectors should be straightforward hyphenated lowercase — but I’d try not to use them anyway.) [SelectorFormat]

Rule Set Innards

» Include a line break between each declaration. Keep declarations isolated: that way you can monitor and discipline them more easily. [SingleLinePerProperty]

» Include quotation marks around URLs. SCSS-Lint documentation explains why. [UrlQuotes]

» Use shorthand properties when you can. As is the case with husbands: shorter is better. [Shorthand]

» Do not include units on zero values. It could be zero of anything: we don’t have to know. [ZeroUnit]

» Use border: 0 instead of border: none. Cuz. [BorderZero]

» Do not nest selectors more than 3 levels deep. Zero levels of nesting is best, of course. One level is often handy, and arguably helps organize some common code (especially pseudo-classes and pseudo-elements). Two or three raises eyebrows and specificity. More invites (induces?) disaster. [NestingDepth]

// best
.horse {
  font-size: 10em;

  // handy
  &:hover {
    font-size: 20em;

    // eyebrow-raising
    & > .donkey {
    color: #eee;

      // disaster-inviting(-inducing?)
      & + .mule {
        margin: 100em;

        // disaster-in-progress
        ul li a span {
          color: $death;
        }
      }
    }
  }

» Do not type vendor prefixes. You have too much life to live. Use Autoprefixer — or, at the very least, Bourbon or Compass mixins. [VendorPrefixes]

» Order declarations in a logical manner. Here’s one logical manner that SCSS-Lint can enforce: (A) @extend directives first; then (B) @include directives without inner @content; then (C) vanilla CSS declarations, with their properties and values; then (D) @include directives with inner @content; then (E) nested statements (rule sets and at-rules). [DeclarationOrder]

Why? (This is going to get weird.) A and B belong at the top so that C and E can override them as needed — whereas A and B should never need to override C or E, I think, or something has gone sour and refactoring is preferable. E is at the end so that it can override anything. And D is where it is because God only knows what it’s going to do. (I’m not sure this logic is 100% foolproof, but I’m giving it a try. We can’t simply be spectators to chance, can we? We must make choices and live with the consequences. There’s a movie about that, right?)

Colors

» Do not use color keywords — such as green instead of #008000. Color keywords are for children, aging parents, and pranks, not the robot-communication we all aspire to. [ColorKeyword]

» Use the shortest possible hex value. #fff instead of #ffffff. (See the statement about husbands, above.) [HexLength]

» Use lowercase letters inside hex values. #fff instead of #FFF. Unnecessary capital letters are obnoxious to the sensitive soul. And, again, why use shift when you don’t have to? Last reason: lowercase letters are, I think, a little bit easier to distinguish from numbers. I’m not making that up. [HexNotation]

Numbers

» Do not include unnecessary trailing zeros after a decimal point. 2em instead of 2.0em. [TrailingZero, UnnecessaryMantissa]

» Do include an unnecessary leading zero before decimal values less than 1. 0.5em instead of .5em. Don’t you think it improves readability? I do. [LeadingZero]

Sassy Things

» @extend placeholders only. This practice will help prevent some common @extend-related mistakes and misunderstandings. [PlaceholderInExtend]

» Include one space before any ! and no spaces after it, with !important and !default declarations. That’s just how it’s done. [BangFormat]

» Put @else on the same line as the curly brace it follows. [ElsePlacement]

// bad
@if {
  // ...
}
@else {
  // ...
}

// good
@if {
  // ...
} @else {
  // ...
}

» Do not include leading underscores or filename extensions in the basenames of SCSS files that you @import. @import 'horse/donkey' instead of @import 'horse/_donkey.scss';. [ImportPath]

Avoiding Mistakes

» Do not define any property more than once within a single rule set. You probably did it by accident, anyway. [DuplicateProperty]

» Do not leave @debug statements in your code. Clean up after yourself. [DebugStatement]

» Do not leave empty rule sets in your code. Pay attention. [EmptyRule]

» Only use three or six hexidecimal characters. Nothing else means anything, right? [HexValidation]

» Do not misspell properties. In fact, do not misspell anything, ever. [PropertySpelling]