Bugcrowd design system: Our Sass guidelines

Sass is pretty awesome.

This section is about harnessing that awesomeness while keeping our styling code sane, maintainable, and DRY.

A lot of this material coincides with advice set out in the Sass Guidelines Project. That style guide goes well beyond what’s covered here; it’s a worthwhile read.

BEM: block, element, modifier

Last updated: Sep 25, 2017

Use BEM as a naming schema for classes.

BEM example

Consider:

.person {}
.person__hand {}
.person--female {}
.person--female__hand {}
.person__hand--left {}

Nested child blocks of a block are defined using a double underscore.

Variations on a block use hyphens. For example, .button would be the base style, with .button--error and .button--success as modifiers.

The block should hold all the necessary styling for that block, whereas the modifier selector should only add or override its necessary styles, ideally in explicit/long-form.

Note: nested blocks are flattened — we don’t go overboard.

Don’t do this:

.parent-block__child-block__element { }

Do this:

.parent-block__element { }

This is still verbose enough to denote that the class expects to be nested inside .parent-block).

Further reading

Specificity and Sass nesting

Last updated: Sep 25, 2017

Selectors should be written as flat as possible, avoiding any Sass nesting unless explicitly needed to increase specificity.

Nesting — while powerful, and a main feature of Sass — can produce very specific, long output CSS selectors.

When avoided unless necessary, and followed alongside a consistent class naming scheme, the CSS’ cascading becomes predictable, and in turn lowers the need for !important throughout the codebase.

Example of flatter Sass nesting using BEM

Do this:

// .bc-body comes from Foundations > Body.
.bc-body {
  // styles…

  a {
    text-decoration: underline;
  }
}

// Your partial:

.block { }

.block__element { }

.block__element--modifier {
  // styles…

  .bc-body a & {
    text-decoration: none;
  }
}

Don’t do this:

// .bc-body comes from Foundations > Body.
.bc-body {
  // styles…

  a {
    text-decoration: underline;
  }
}

// Your partial:

.block {
  // styles…

  .block__element {
    // styles…

    .block__element--modifier {
      // styles…

      a {
        text-decoration: none !important;
      }
    }
  }
}

Writing clear selectors

Last updated: Sep 25, 2017

Write class-based selectors.

The design system uses classes, sometimes with nested selectors for specific HTML elements.

Do not use IDs in selectors, even when required for some specific purpose (eg linking). There are legitimate use cases for giving an element an ID, eg <main> and the primary <nav> often have IDs so that we can link to them.

In such cases add a class to the element that required the ID, and use it in selector — even if it shares the same as the ID.

Example of class-based selectors

Do this:

.main-content { }
.main-navigation { }

Don’t do this:

#main-content { }
#main-navigation { }

IDs raise specificity unnecessarily and their usage scales poorly in medium+ sized codebases.

Short & long-form CSS notation

Last updated: Apr 17, 2018

Short-form notation is fine, but prefer explicit, long-form property notation for overrides.

Short-form notation is A-OK when setting a number of things, but be careful.

Long-form is more explicit/declarative, and should be favoured to avoid unintentionally overriding a style, or adding something that will later cascade undesirably to a child.

Example of long-form over short-form notation

Imagine we’ve got styling for .foo:

.foo {
  background:
    url(pattern.jpg)    /* -image */
    red;                /* -color */
  margin: 0 auto;
}

…and then a bit later we want to add a BEM modifier for the .foo block, say .foo--variant where we want to change the background image, its background color, and the top margin:

Don’t do this:

.foo--variant {
  background-color: blue:
  background: url(texture.jpg);
  margin: 1em auto 0 auto
}

Do this:

.foo--variant {
  background-color: blue:
  background-image: url(texture.jpg);
  margin-top: 1em;
}

If you use the first --variant code block, what background color is .foo? Transparent.

Why? Because the short-form overrides the more explicit background-color property. This might not be a problem for us, and if it is, one solution could be to simply put background-color after the background property, but even cleaner would be to agree that all overrides are explicitly written in long form.

Of course, if all four margins are being reset, then using the margin short-form is entirely fine.

Avoid custom vendor prefixes

Last updated: Apr 17, 2018

Avoid hand-writing vendor prefixes.

There are experimental [vendor] prefixes, and experimental non-standard prefixes.

Hand-writing and updating vendor prefixes quickly becomes unmaintainable.

An autoprefixer handles automatic addition/substitution at Sass compilation of the properties that have support in current browsers.

Don’t do this:

.foo {
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;
}

Do this:

.foo {
  appearance: none;
}

Let the autoprefixer do the work for us.

Exceptions

Where rare exceptions are necessary, comment why the vendor prefix is added, eg:

.foo {
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  // text-size-adjust is non-standard, but implemented,
  // and improves readability.

  a {
    text-decoration: underline;
    webkit-text-decoration-skip: objects;
    // text-decoration-skip is non-standard, but implemented,
    // and improves legibility of descender glyphs with
    // text-decoration: underline.
  }
}

Being careful with @extend

Last updated: Sep 25, 2017

Use @extend wisely; opt for mixins instead.

Use mixins (mixins without arguments are legit) for reusable code snippets. Avoid the use of @extend unless a relationship needs to be made explicit.

Example of the problem with @extend

The issue is that @extend will indiscriminately extend every instance of a matching selector that comes across. For example:

Don’t do this:

.foo { color: red; }

.footer .foo { font-weight: bold; }

.bar { @extend .foo; }

You might have been expecting that to compile into:

.foo, .bar { color: red; }

.footer .foo { font-weight: bold; }

But because @extend establishes relationships you will find you instead get:

.foo, .bar { color: red; }

.footer .foo, .footer .bar {
  font-weight: bold;
}

This can result in absolutely enormous selector strings (aka ‘selector explosion’).

Instead use selector placeholders or ‘silent classes’ (%foo) like in the example below…

.foo,
%foo {
  color: red;
}

.footer .foo {
  font-weight: bold;
}

.bar {
  @extend %foo;
}

…or better yet, just use mixins.

For more information pop over to the Sass Guidelines Project entry on @extend.

Code commenting and spacing

Last updated: Sep 25, 2017

Document (almost) all the things, using the SassDoc format commenting format.

Quoting from the Sass Guidelines:

CSS is a tricky language, full of hacks and oddities. Because of this, it should be heavily commented, especially if you or someone else intend to read and update the code 6 months or 1 year from now. Don’t let you or anybody else be in the position of I-didn’t-write-this-oh-my-god-why.

Generally:

  • Title and briefly describe any non-trivial declaration blocks with C-style comments, eg:

    **
     * Block, function, mixin, ... title
    */
    
  • Document functions and mixins using the global SassDoc format using inline comments with an extra slash (///), eg:

    /// Replace a string with a string
    /// http://codepen.io/jakob-e/pen/doMoML
    ///
    /// @author @eriksen_dk <https://twitter.com/eriksen_dk>
    ///
    /// @param  {string} $string   - The haystack string to be manipulated
    /// @param  {string} $search   - The needle to be replace
    /// @param  {string} $replace  - The replacement
    ///
    /// @return {string}           - The manipulated string with replaced values
    
  • Space declaration blocks (unless nested) with two blank lines.
  • When documenting specific properties or values, use stock-standard inline comments (//) on the same line if your comment fits within 80 characters for that line (including the code), or on the following line.

Writing responsive selectors

Last updated: Sep 25, 2017

Write mobile-first responsive styles, with media queries inside selectors.

Mobile-first

A webpage is ‘mobile-first’ if the page loads well for mobile viewports when all media queries are disabled or removed; the remaining selectors provide styling for small viewports first.

Where possible write mobile-first styles, and then layer tablet and desktop styles ‘on top’ through media queries.

This approach is superior to its opposite, where media query rules contain tablet and mobile styling, effectively undoing the (default) desktop styles, since the latter is usually more complex than mobile styles.

Nest responsive styles inside what it modifies

When adding media queries, nest them inside the selector that is being modified. This keeps related styles together.

Example of mobile-first styles

Do this:

.bc-body--guide {
  margin: 3em 1.5em; // mobile (default)

  // ≥768px: let’s pad sides a bit more
  @include bc-media(sm) {
    margin-left: 4em;
    margin-right: 4em;
  }

  // ≥992px: now center the page design
  @include bc-media(md) {
    max-width: 52em;
    margin-left: auto;
    margin-right: auto;
  }
}

Don’t do this:

.bc-body--guide {
  max-width: 52em;
  margin: 3em auto 1.5em auto;
}

@include bc-media(sm) {
  .bc-body--guide {
    margin-left: 4em;
    margin-right: 4em;
    max-width: none; // needs resetting…
  }
}

@include bc-media(xs) {
  .bc-body--guide {
    margin-left: 1.5em;
    margin-right: 1.5em;
  }
}