CSS Standards and Normalization Part 1: Philosophy

Purpose

The purpose of this series will be to define some standardized best practices for how CSS is composed and organized in our projects.

This is part 1 of 4: Philosophy. In this article, we will explore our CSS philosophy and provide a high-level overview of how we write css.

Principles

Our CSS philosophy borrows from several different frameworks and systems:

The Goals of Drupal 8’s CSS philosophy serve as a good starting point. Well-architected CSS should be:

1. Predictable

CSS throughout Drupal core and contributed modules should be consistent and understandable. Changes should do what you would expect without side-effects.

2. Reusable

CSS rules should be abstract and decoupled enough that you can build new components quickly from existing parts without having to recode patterns and problems you’ve already solved. – Philip Walton, CSS Architecture

3. Maintainable

As new components and features are needed, it should be easy to add, modify and extend CSS without breaking (or refactoring) existing styles.

4. Scalable

CSS should be easy to manage for a single developer or for large, distributed teams (like Drupal’s).

Source: CSS architecture (for Drupal 8)

In addition, we should keep Mark Otto’s Golden Rule in mind:

Every line of code should appear to be written by a single person, no matter the number of contributors.

Code Guide by Mark Otto

Code Structure & Style

Here’s an overview of how we structure and organize our code (borrowed from css guidelines):

Sass

Sass is our CSS preprocessor of choice. Sass is bracket-less {} and does not use semi-colons to terminate lines. Overall it provides a cleaner feel to the code.

Each project should include Sass linting. Use our default .sass-lint.yml file. It will point out the rules below for you.

Four space indents

Since we aren’t using brackets, this keeps the Sass clearer and also is aligned with our Javascript and PHP standards. Most code editors can be set up to use four spaces as the default line indent.

Multi-line css

Selectors and properties each get their own line. This makes code legible and easier to parse visually

Do it like this:

.container
    width: 95%
    max-width: 40em

Not like this (with no brackets in Sass, single-line format is not valid):

.container{width: 95%; max-width: 40em;}

Meaningful use of white space

White space can give context to class definitions. Include two empty lines between top-level css blocks, and one line between properties and nested classes.

Example:

.card
    padding: 1rem
    margin: 1rem

    &__header // leave one empty line above this since it is nested
        border-bottom: 1px solid gray


.card--white // two empty lines above this new class block
    background-color: white


Limit Sass nesting depth to 2 levels

Nesting depth should be limited to 2. This prevents un-necessary specificity of selectors and makes future maintenance easier. The code is also easier to understand with limited nesting depth.

Example:

.block
    &__element
        &--modifier // Deepest Nest Allowed


Limit line length to 80 characters

This helps make the code more readable without horizontal scrolling. Because you’re writing multi-line css, this should really only come into play for comments. Limiting your line length will make your comments easier to read and therefore more useful. For instances like long urls or gradient syntax, don’t worry about it. Most code editors also have a setting for maximum line length.

Example:

// This file is where you override default Bootstrap variables. You
// can find a list of the default Bootstrap variables
// in _variables.scss

Use single line comments

Your comments don’t have to be on one single line, just don’t use traditional multi-line comment format, ie: /* These kinds of comments */, because they will end up in the compiled css. Your comments, even if they take up multiple lines, should look like this:

// This file is where you override default Bootstrap variables. You
// can find a list of the default Bootstrap variables
// in _variables.scss

Clean Import paths

You don’t need to include leading underscores or filename extensions in your import paths. To stay consistent, your imports should look like this:

@import "base/typography" // where this file is base/_typography.sass
@import "base/colors"

@import "layout/grid"
@import "layout/containers"

Writing Selectors

We have a few preferences when it comes to writing CSS selectors.

Write Selectors for Humans

Class names should use full words rather than abbreviations. Remember that your class names are written for the benefit of other developers, not the computer. Prefer class="block" to class="blk".

Class names for components should use dashes between words for legibility (not underscores). Prefer class="component-name" to class="componentname".

Class Name Format (Using BEM)

We use a BEM-style class naming system. BEM stands for Block Element Modifier. You can also think about it as Component, Sub-object, Variant. At a high level, BEM seeks to:

For more on BEM philosophy: BEM Philosophy

More on how we interpret BEM philosophy will be explored in later articles. In general, BEM treats the highest level of a component as a “Block”. A site menu is a good example of a “Block”:

.primary-menu
    //some menu styles

Related sub-parts of a “Block” are considered “Elements.” In this example, items in the menu would be “Elements”. Different elements are each given their own unique class name. Element class names use their block’s class name, followed by 2 underscores, then the element’s class name. Elements are NOT nested underneath their block selector to avoid contextual dependency. Like so:

//Like This
.primary-menu
    //some menu styles

.primary-menu__menu-item
    //styles for the menu item
    
//Not Like This
.primary-menu
    //some menu styles
    .primary-menu__menu-item
        //styles for the menu item
        //this class must now always be a child of .primary-menu for these styles to apply

“Modifiers” or “variants” of elements create slightly different versions of an element. To create a modifier, add two dashes and the modifer name to the end of the element class name. For our menu example, lets say we want some menu items to have a bottom-border.

.primary-menu__menu-item--border-bottom
    //styles for the variant menu item with a bottom border
    border-bottom: 1px solid #000

If you prefer to leverage Sass’s nesting capabilities, the example above can be written like so:

.primary-menu
    //some menu styles
    &__menu-item
        //styles for the menu item
        &--border-bottom
        //styles for the variant menu item with a bottom border
        border-bottom: 1px solid #000

When using BEM naming, you should not write classes with more than 1 set of double underscores (__). Using the BEM approach, each “Element” gets its own class, they do not depend on each other. For example, If you had an <a> tag inside your primary-menu__item, it may tempting to add a class of primary-menu__menu-item__link. This implies element dependency, that the link element depends on being inside the menu item. Element dependency is something BEM strives to avoid. You would instead add a class to the <a> element of primary-menu__link.

// Do this
.primary-menu__menu-item
    //menu item styles
.primary-menu__link
    //link styles

// Don't do this
.primary-menu__menu-item__link
    //nope nope nope

Avoid Using id Selectors

You can use them for Javascript or for providing anchor-links, but don’t use them for styling.

Avoid Vendor Prefixes

For sites that are using a build tool like Grunt or Gulp, we can skip vendor prefixes in the css in favor of using the PostCSS Autoprefixer plugin. It will automatically determine what prefixes are necessary based on browser support requirements and only include the necessary ones. If you build the auto-prefixer into a tooling-chain, don’t do it on every compile during dev, it can slow down your build time.

Use relative units for font-sizing.

Prefer relative units like rem or em for font-sizing. This allows for more flexible, more maintainable font styling. It also allows us to better control font styles based on what fonts have been loaded or not loaded.

Likewise, avoid specifying units for line-height. Line-height should be a ratio of font-size. You will need to write a lot less css if your line-height ratios are on to begin with.

Namespacing

Consider name-spacing layout and javascript specific classes.

For instance, instead of using a class name like .container, you could use .layout-container. Or .layout-grid, over .grid. This gives the benefit of clear separation between component-specific and layout-specific styles.

Or for javascript that is manipulating the DOM, use something like .js-behavior-hook instead of .behavior-hook to make sure that it is a dedicated class not used for styling.

Source: Drupal 8 CSS Architecture | Class Name Formatting

Linting

The easiest way to put these guidelines into practice will be to use sass-lint. You can install it globally like so: npm install -g sass-lint or on a per-project basis like this: npm install sass-lint --save-dev

Add a copy of our .sass-lint.yml to the root of your project.

To lint your project from the command line, you can do this:

sass-lint -v

Or, for as-you-go linting, install the sass-lint plugin for your respective editor:

sass-lint documentation

Resources

We leaned heavily on the following resources for these guidelines: