Stylesheets Claws

Introduction

CSS is a very powerful but dangerous weapon. On one hand, it allows you to build extraordinary things, but on the other hand it can have an awful impact on your app’s scalability and maintainability if not handled carefully.

There are a lot of bad habits that can damage your stylesheets. Things like overwriting specific rules multiple times, using “magic numbers” or overqualified selectors are known as “code smells” and are signs of unhealthy CSS.

The fact that CSS is a highly opinionated language and that it applies on a global scope, makes it hard for developers to collaborate and keep a scalable and maintainable codebase without a set a guides and rules.

Syntax and Formatting

  • Use soft-tabs with a four space indent. Spaces are the only way to guarantee code renders the same in any person’s environment.
    .element {
        display: block;
    }
  • Include one space before the opening brace of declaration blocks for legibility.
    .selector { }
  • Include one space after : in property declarations.
    .selector { display: block; }
  • Place closing braces of declaration blocks on a new line.
    .selector {
        display: block;
        height: auto;
    }
  • Put line breaks between rulesets.
    .selector-1 {
        display: block;
        height: auto;
    }
     
    .selector-2 {
        display: inherit;
    }
  • When grouping selectors, keep individual selectors to a single line.
    .selector,
    .another-selector,
    .and-another {
        display: block;
        height: auto;
    
        color: #FFFFFF;
        background-color: #000000;
    }
  • Each declaration should appear on its own line for more accurate error reporting.
  • End all declarations with a semicolon. The last declaration's semicolon is optional, but your code is more error prone without it.
    .selector {
        display: block;
        height: auto;
    
        color: #FFFFFF;
        background-color: #000000;
    }
  • Comma-separated property values should include a space after each comma (e.g., box-shadow).
    .selector {
        box-shadow: inset 5px 5px 10px #000000, inset -5px -5px 10px #454545, inset -10px -5px 12px #334332;
    }
  • Don't include spaces after commas within rgb(), rgba(), hsl(), hsla(), or rect() values. This helps differentiate multiple color values (comma, no space) from multiple property values (comma with space).
    .selector {
        background-color: rgb(0, 0, 0);
        background-color: rgb(0,0,0);
    }
  • Don't prefix property values or color parameters with a leading zero (e.g., .5 instead of 0.5 and -.5px instead of -0.5px).
    .selector {
        opacity: 0.5;
        opacity: .5;
    }
  • Lowercase all HEX values within the same declaration. Lowercase letters are much easier to discern when scanning a document as they tend to have more unique shapes.
    .selector {
        box-shadow: 10px #FAFAFA, -5px #ccc, 12px #DDD;
        box-shadow: 10px #fafafa, -5px #ccc, 12px #ddd;
    }
  • Quote attribute values in selectors. They’re only optional in some cases, and it’s a good practice for consistency.
    input[type=text] { ... }
    input[type="text"] { ... }
  • Avoid specifying units for zero values.
    .selector { margin: 0px; }
    .selector { margin: 0; }
  • Use HEX color codes unless using rgba() in raw CSS (SCSS’ rgba() function is overloaded to accept hex colors as a parameter).
    // SCSS
    .selector {
        background-color: rgba(0,0,0, .5);
        background-color: rgba(#000000, .5);
    }
    
    /* CSS */
    .selector {
        background-color: rgba(0,0,0, .5);
    }

Single declarations

In instances where a rule set includes only one declaration, consider removing line breaks for readability and faster editing. Any rule set with multiple declarations should be split to separate lines.

.selector { display: block; }

The key factor here is error detection—e.g., a CSS validator stating you have a syntax error on Line 183. With a single declaration, there's no missing it. With multiple declarations, separate lines is a must for your sanity.

Declaration order

We group by type within that scope we toggle between arbitrary order and order by line length. Types are ordered by their importance, from loose to more specific properties.

  • Extends
  • Positioning – position, top, right, bottom, left, z-index
  • Display, flex properties, and box model
  • Border and other of its properties(style, width, color)
  • Background and color
  • Typographic
  • Other
.element {
    @extend %font-size-16;
    @extend %clearfix;

    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1;

    display: flex;
    flex: 1 1 auto;
    height: auto;
    @import width-full(); /* width: 100%; */

    border: 1px solid #fff;
    border-radius: 5px;

    color: #cecece;
    background-color: #000;

    font-family: sans-serif;
    font-size: 100%;
    font-style: regular;
    text-align: center;
    text-transform: uppercase;

    opacity: 1;
    transform: none;
    pointer-events: auto;
}

@todo we should have a reference to a .csscomb.json file and explain the reasoning behind it

https://github.com/pixelgrade/components/blob/master/.csscomb.json

Shorthand notation

@todo maybe use the precss postcss plugin that allows you to write things like margin: * auto; to avoid accidentally overwriting unnecessary values.

Strive to limit use of shorthand declarations to instances where you must explicitly set all the available values. Common overused shorthand properties include:

  • padding
  • margin
  • font
  • background
  • border
  • border-radius
.selector {
    /* margin: 0 auto; */
    margin-left: auto;
    margin-right: auto;
}

Often times we don't need to set all the values a shorthand property represents. For example, HTML headings only set top and bottom margin, so when necessary, only override those two values. Excessive use of shorthand properties often leads to sloppier code with unnecessary overrides and unintended side effects.

Commenting

Double slash // comments should be used for comment blocks in SCSS(instead of /* */). A notable exception would be in _main.scss described above.

/* Comment in SCSS - valid but not encouraged */
// Comment in SCSS - valid and encouraged 

/* Comment in CSS */

_main.scss should have a table of contents. The table of contents will have CSS comments /* */ so that it will be output in the CSS file. All .scss files from all folders will be included in _main.scss.

Example: _main.scss

/**
 * Table of Contents
 *
 * 1.0 - Reset
 * 2.0 - Base Styles
 * 3.0 - Abstractions
 *   3.1 - Grid
 *   3.2 - Media
 * 4.0 - Components
 *   4.1 - Header
 *   4.2 - Navigation
 *   4.3 - Footer
 *   4.4 - Card
 *   4.5 - Button
 *   4.6 - Small Links
 *   4.7 - Intro
 *   4.8 - Dropcap
 *   4.9 - Search Form
 *   4.10 - Comments Area
 *   4.11 - Tags
 *   4.12 - Post Navigation Links
 *   4.13 - Widgets
 *   4.14 - Infinite Scroll
 * 5.0 - Page Specific Styles
 *   5.1 - Singular
 * 6.0 - Overwrites
 */

// Settings
@import "settings/default";

// Tools
@import "tools/clearfix";
@import "tools/helpers";
@import "tools/queries";
@import "tools/easings";
@import "tools/font-face";
@import "tools/wp-offset";



/**
 * 1.0 - Reset
 */
@import "generic/reset";
@import "generic/box-sizing";



/**
 * 2.0 - Base Styles
 */
@import "base/typography";
@import "base/blockquote";
@import "base/pre";
@import "base/tables";
@import "base/titles";
@import "base/form-elements";
@import "base/wp-align";



/**
 * 3.0 - Abstractions
 *   3.1 - Grid
 */
@import "objects/grid";

/**
 *   3.2 - Media
 */
@import "objects/media";

(...)

_main.scss will be included in style.scss that contains holds CSS comments with pieces of information(e.g. WordPress theme version) that will appear as comments in the generated style.css file.

Example: style.scss


@charset "UTF-8";
/*
Theme Name: Hive
Theme URI: http://pixelgrade.com/demos/hive/
Author: Pixelgrade
Author URI: http://pixelgrade.com
Description: An effortless tool for publishers of all kind, cherished for its clean masonry-style layout (...)
Version: 1.2.5-wpcom
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: hive_txtd
Domain Path: /languages/
Tags: blog, bright, clean, custom-menu, editor-style, elegant, fashion, featured-images, geometric (...)

This theme, like WordPress, is licensed under the GPL.
Use it to make something cool, have fun, and share what you've learned with others.

Hive is based on Underscores http://underscores.me/, (C) 2012-2014 Automattic, Inc.
*/

@import "main";
	

Architecture

The Inverted Triangle CSS Methodology is a scalable and maintainable CSS architecture introduced by Harry Roberts which gives you a set of rules to help you deal with CSS specifics like global namespace, cascade and selector specificity.

One of the key principles of ITCSS is that it separates your CSS codebase to several sections (called layers), which take form of the inverted triangle:

  • Settings — global settings and variables declarations.
  • Tools — globally used mixins and functions.
  • Generic — reset and / or normalize styles.
  • Elements — styling for bare HTML elements
  • Objects — class-based selectors which define undecorated design patterns
  • Components — specific UI components
  • Trumps — utilities and helper classes with ability to override anything
  • Shame — the place for prototyping, quick fixes and overrides

Folder Structure

|-- base
|-- components
|-- generic
|-- tools
    |-- _mixins.scss
|-- objects
|-- trumps
|-- vendor
|-- _main.scss
|-- _settings.scss
|-- _shame.scss
|-- style.scss

Given the WordPress environment in which we do our work, most times we will include in our project third-party plugins with their own CSS. These files should reside in the vendor folder and the place in the Inverted Triangle would theoretically be the Components layer.

Naming conventions

In no particular order, here are the individual namespaces and a brief description. We’ll look at each in more detail in a moment, but the following list should acquaint you with the kinds of thing we’re hoping to achieve.

Objects: o-

Signify that the element which it is applied to is an Object. It can be used for layout purposes or abstracting visual patterns. These elements’ properties shouldn’t be modified by other elements or based on context. Modifying the object itself should be avoided.

Components: c-

Components are well-defined pieces of UI and their properties are mostly for decoration purposes. The scope to which changes to these elements’ styling would apply is easily to detect and explain.

Utility: u-

Utilities classes are in no way attached to any piece of ui or code. Each class has a specific purpose and they come from methodologies like Tachyons / Basscss / Atomic CSS. They are more suited when developing for an environment where you have full control over the markup but that’s not the case for WordPress.

State: is-, has-

State classes define a scope which each elements are styled in a certain way cause of the current state of the application. They are usually temporary or optional

JavaSript Hooks: js-

These classes don’t have any stylistic purposes and are used to bind JavaScript to them for a certain behavior.

CSS Selectors

The absence of scope in CSS is one of the main reasons code gets bad. Nesting is not an option since it brings unwanted specificity to your selectors. This leaves us with the only option of using a naming convention for the class names used.

BEM

http://getbem.com/naming/

BEMIT

http://csswizardry.com/2015/08/bemit-taking-the-bem-naming-convention-a-step-further/

An ID / class should be as short as possible but as long as necessary

Dealing with specificity

  • Never use IDs in CSS, ever. They have no advantage over classes (anything you can do with an ID, you can do with a class), they cannot be reused, and their specificity is way, way too high. Even an infinite number of chained classes will not trump the specificity of one ID.
  • Do not nest selectors unnecessarily. If .header-nav {} will work, never use .header .header-nav{}; to do so will literally double the specificity of the selector without any benefit.
  • Do not qualify selectors unless you have a compelling reason to do so. If .nav {} will work, do not use ul.nav {}; to do so would not only limit the places you can use the .nav class, but it also increases the specificity of the selector, again, with no real gain.
  • Make heavy use of classes because they are the ideal selector: low specificity (or rather, all classes have the same specificity, so you have a level playing field), great portability, and high reusability.

Selecting IDs

<div id="widget">
	...
</div>

Naturally, given that we can’t edit this HTML to use a class instead of (or alongside) the ID, we’d opt for something like this:

#widget {
    ...
}

Now we have an ID in our CSS, and that is not a good precedent to set. Instead, we should do something like this:

[id="widget"] {
    ...
}

This is an attribute selector. This is not selecting on an ID per se, but on an element with an attribute of id which also has a certain value.

The beauty of this selector is that it has the exact same specificity as a class, so we’re selecting a chunk of the DOM based on an ID, but never actually increasing our specificity beyond that of our classes that we’re making liberal use of elsewhere.

Just because we know a way of using IDs without introducing their heightened specificity, it does not mean we should go back to using IDs in our CSS; they still have the problem of not being reusable. Only use this technique when you have no other option, and you cannot replace an ID in some markup with a class.

Maybe it's not that !important

!important should be avoided at all costs. There are certain cases where !important can or should be used. For example utility classes and / or when you want to overwrite some inline CSS which for some reason cannot be removed. In this case there should be a CSS comment above the line where !important is used to explain the reason it was used.

Safely increasing specificity

You can chain a selector with itself to increase its specificity. That is to say .btn.btn {} will apply to the same elements as .btn {} but with double the specificity. We can take this as far as we need to: .btn.btn.btn.btn {} but hopefully we’ll never get that far.

Closing Remarks

Mixins and @extend

Mixins are powerful tools which can be used to leverage your work and also handy shortcuts. The most valuable use of mixins is generating CSS. It is very helpful for having DRY code and changes in different modules come with less effort. We should at most times try to keep the declaration and usage of mixins inside the same files so we don’t create any unneeded dependencies. The same applies for @extend which must not be used inside components! Components should be independent chunks of code with high portability. Also using @extend proves to be less performant than using mixins (probably even repeating code) when the source is gzipped. (see http://csswizardry.com/2016/02/mixins-better-for-performance/)

Keep your codebase clean

  • Organize sections of code by component.
    • Develop a consistent commenting hierarchy.
  • Use consistent white space to your advantage when separating sections of code for scanning larger documents.
  • When using multiple CSS files, break them down by component instead of page. Pages can be rearranged and components moved.

  • References: