HTML

Given HTML and CSS’ inherently interconnected nature, it would be remiss of me to not cover some syntax and formatting guidelines for markup.

Always quote attributes, even if they would work without. This reduces the chance of accidents, and is a more familiar format to the majority of developers. For all this would work (and is valid):

…this format is preferred:

The quotes are not required here, but err on the safe side and include them.

When writing multiple values in a class attribute, separate them with two spaces, thus:

When multiple classes are related to each other, consider grouping them in square brackets ([ and ]), like so:

This is not a firm recommendation, and is something I am still trialling myself, but it does carry a number of benefits. Read more in Grouping related classes in your markup.

As with our rulesets, it is possible to use meaningful whitespace in your HTML. You can denote thematic breaks in content with five (5) empty lines, for example:


    ...

Separate independent but loosely related snippets of markup with a single empty line, for example:

This allows developers to spot separate parts of the DOM at a glance, and also allows certain text editors—like Vim, for example—to manipulate empty-line-delimited blocks of markup.

Further Reading


Commenting

The cognitive overhead of working with CSS is huge. With so much to be aware of, and so many project-specific nuances to remember, the worst situation most developers find themselves in is being the-person-who-didn’t-write-this-code. Remembering your own classes, rules, objects, and helpers is manageable to an extent, but anyone inheriting CSS barely stands a chance.

CSS needs more comments.

As CSS is something of a declarative language that doesn’t really leave much of a paper-trail, it is often hard to discern—from looking at the CSS alone—

  • whether some CSS relies on other code elsewhere;
  • what effect changing some code will have elsewhere;
  • where else some CSS might be used;
  • what styles something might inherit (intentionally or otherwise);
  • what styles something might pass on (intentionally or otherwise);
  • where the author intended a piece of CSS to be used.

This doesn’t even take into account some of CSS’ many quirks—such as various sates ofoverflow triggering block formatting context, or certain transform properties triggering hardware acceleration—that make it even more baffling to developers inheriting projects.

As a result of CSS not telling its own story very well, it is a language that really does benefit from being heavily commented.

As a rule, you should comment anything that isn’t immediately obvious from the code alone. That is to say, there is no need to tell someone that color: red; will make something red, but if you’re using overflow: hidden; to clear floats—as opposed to clipping an element’s overflow—this is probably something worth documenting.

High-level

For large comments that document entire sections or components, we use a DocBlock-esque multi-line comment which adheres to our 80 column width.

Here is a real-life example from the CSS which styles the page header on CSS Wizardry:

/**
 * The site’s main page-head can have two different states:
 *
 * 1) Regular page-head with no backgrounds or extra treatments; it just
 *    contains the logo and nav.
 * 2) A masthead that has a fluid-height (becoming fixed after a certain point)
 *    which has a large background image, and some supporting text.
 *
 * The regular page-head is incredibly simple, but the masthead version has some
 * slightly intermingled dependency with the wrapper that lives inside it.
 */

This level of detail should be the norm for all non-trivial code—descriptions of states, permutations, conditions, and treatments.

Object–Extension Pointers

When working across multiple partials, or in an OOCSS manner, you will often find that rulesets that can work in conjunction with each other are not always in the same file or location. For example, you may have a generic button object—which provides purely structural styles—which is to be extended in a component-level partial which will add cosmetics. We document this relationship across files with simple object–extension pointers. In the object file:

/**
 * Extend `.btn {}` in _components.buttons.scss.
 */
.btn {}

And in your theme file:

/**
 * These rules extend `.btn {}` in _objects.buttons.scss.
 */
.btn--positive {}
.btn--negative {}

This simple, low effort commenting can make a lot of difference to developers who are unaware of relationships across projects, or who are wanting to know how, why, and where other styles might be being inherited from.

Low-level

Oftentimes we want to comment on specific declarations (i.e. lines) in a ruleset. To do this we use a kind of reverse footnote. Here is a more complex comment detailing the larger site headers mentioned above:

/**
 * Large site headers act more like mastheads. They have a faux-fluid-height
 * which is controlled by the wrapping element inside it.
 *
 * 1. Mastheads will typically have dark backgrounds, so we need to make sure
 *    the contrast is okay. This value is subject to change as the background
 *    image changes.
 * 2. We need to delegate a lot of the masthead’s layout to its wrapper element
 *    rather than the masthead itself: it is to this wrapper that most things
 *    are positioned.
 * 3. The wrapper needs positioning context for us to lay our nav and masthead
 *    text in.
 * 4. Faux-fluid-height technique: simply create the illusion of fluid height by
 *    creating space via a percentage padding, and then position everything over
 *    the top of that. This percentage gives us a 16:9 ratio.
 * 5. When the viewport is at 758px wide, our 16:9 ratio means that the masthead
 *    is currently rendered at 480px high. Let’s…
 * 6. …seamlessly snip off the fluid feature at this height, and…
 * 7. …fix the height at 480px. This means that we should see no jumps in height
 *    as the masthead moves from fluid to fixed. This actual value takes into
 *    account the padding and the top border on the header itself.
 */
.page-head--masthead {
    margin-bottom: 0;
    background: url(/img/css/masthead.jpg) center center #2e2620;
    @include vendor(background-size, cover);
    color: $color-masthead; /* [1] */
    border-top-color: $color-masthead;
    border-bottom-width: 0;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) inset;
    @include media-query(lap-and-up) {
        background-image: url(/img/css/masthead-medium.jpg);
    }
    @include media-query(desk) {
        background-image: url(/img/css/masthead-large.jpg);
    }
    > .wrapper { /* [2] */
        position: relative; /* [3] */
        padding-top: 56.25%; /* [4] */

        @media screen and (min-width: 758px) { /* [5] */
            padding-top: 0; /* [6] */
            height: $header-max-height - double($spacing-unit) - $header-border-width; /* [7] */
        }
    }
}

These types of comment allow us to keep all of our documentation in one place whilst referring to the parts of the ruleset to which they belong.

Preprocessor Comments

With most—if not all—preprocessors, we have the option to write comments that will not get compiled out into our resulting CSS file. As a rule, use these comments to document code that would not get written out to that CSS file either. If you are documenting code which will get compiled, use comments that will compile also. For example, this is correct:

// Dimensions of the @2x image sprite:
$sprite-width:  920px;
$sprite-height: 212px;

/**
 * 1. Default icon size is 16px.
 * 2. Squash down the retina sprite to display at the correct size.
 */
.sprite {
    width:  16px; /* [1] */
    height: 16px; /* [1] */
    background-image: url(/img/sprites/main.png);
    background-size: ($sprite-width / 2 ) ($sprite-height / 2); /* [2] */
}

We have documented variables—code which will not get compiled into our CSS file—with preprocessor comments, whereas our CSS—code which will get compiled into our CSS file—is documented using CSS comments. This means that we have only the correct and relevant information available to us when debugging our compiled stylesheets.

Removing Comments

It should go without saying that no comments should make their way into production environments—all CSS should be minified, resulting in loss of comments, before being deployed.


Naming Conventions

Naming conventions in CSS are hugely useful in making your code more strict, more transparent, and more informative.

A good naming convention will tell you and your team

  • what type of thing a class does;
  • where a class can be used;
  • what (else) a class might be related to.

The naming convention I follow is very simple: hyphen (-) delimited strings, with BEM-like naming for more complex pieces of code.

It’s worth noting that a naming convention is not normally useful CSS-side of development; they really come into their own when viewed in HTML.

Hyphen Delimited

All strings in classes are delimited with a hyphen (-), like so:

.page-head {}
.sub-content {}

Camel case and underscores are not used for regular classes; the following are incorrect:

.pageHead {}
.sub_content {}

BEM-like Naming

For larger, more interrelated pieces of UI that require a number of classes, we use a BEM-like naming convention.

BEM, meaning Block, Element, Modifier, is a front-end methodology coined by developers working at Yandex. Whilst BEM is a complete methodology, here we are only concerned with its naming convention. Further, the naming convention here only is BEM-like; the principles are exactly the same, but the actual syntax differs slightly.

BEM splits components’ classes into three groups:

  • Block: The sole root of the component.
  • Element: A component part of the Block.
  • Modifier: A variant or extension of the Block.

To take an analogy (note, not an example):

.person {}
.person__head {}
.person--tall {}

Elements are delimited with two (2) underscores (__), and Modifiers are delimited by two (2) hyphens (--).

Here we can see that .person {} is the Block; it is the sole root of a discrete entity..person__head {} is an Element; it is a smaller part of the .person {} Block. Finally,.person--tall {} is a Modifier; it is a specific variant of the .person {} Block.

Starting Context

Your Block context starts at the most logical, self-contained, discrete location. To continue with our person-based analogy, we’d not have a class like .room__person {}, as the room is another, much higher context. We’d probably have separate Blocks, like so:

.room {}
    .room__door {}
.room--kitchen {}.person {}
    .person__head {}

If we did want to denote a .person {} inside a .room {}, it is more correct to use a selector like .room .person {} which bridges two Blocks than it is to increase the scope of existing Blocks and Elements.

A more realistic example of properly scoped blocks might look something like this, where each chunk of code represents its own Block:

.page {}
.content {}
.sub-content {}
.footer {}
    .footer__copyright {}

Incorrect notation for this would be:

.page {}
    .page__content {}
    .page__sub-content {}
    .page__footer {}
        .page__copyright {}

It is important to know when BEM scope starts and stops. As a rule, BEM applies to self-contained, discrete parts of the UI.

Something you need some more help with?

Hire me

More Layers

If we were to add another Element—called, let’s say, .person__eye {}—to this .person {}component, we would not need to step through every layer of the DOM. That is to say, the correct notation would be .person__eye {}, and not .person__head__eye {}. Your classes do not reflect the full paper-trail of the DOM.

Modifying Elements

You can have variants of Elements, and these can be denoted in a number of ways depending on how and why they are being modified. Carrying on with our person example, a blue eye might look like this:

.person__eye--blue {}

Here we can see we’re directly modifying the eye Element.

Things can get more complex, however. Please excuse the crude analogy, and let’s imagine we have a face Element that is handsome. The person themselves isn’t that handsome, so we modify the face Element directly—a handsome face on a regular person:

.person__face--handsome {}

But what if that person is handsome, and we want to style their face because of that fact? A regular face on a handsome person:

.person--handsome .person__face {}

Here is one of a few occasions where we’d use a descendant selector to modify an Element based on a Modifier on the Block.

If using Sass, we would likely write this like so:

.person {}
    .person__face {
        .person--handsome & {}
    }
.person--handsome {}

Note that we do not nest a new instance of .person__face {} inside of .person--handsome {}; instead, we make use of Sass’ parent selectors to prepend .person--handsome onto the existing .person__face {} selector. This means that all of our .person__face {}-related rules exist in once place, and aren’t spread throughout the file. This is general good practice when dealing with nested code: keep all of your context (e.g. all .person__face {}code) encapsulated in one location.

Naming Conventions in HTML

As I previously hinted at, naming conventions aren’t necessarily all that useful in your CSS. Where naming conventions’ power really lies is in your markup. Take the following, non-naming-conventioned HTML:

How are the classes box and profile related to each other? How are the classes profileand avatar related to each other? Are they related at all? Should you be using pro-useralongside bio? Will the classes image and profile live in the same part of the CSS? Can you use avatar anywhere else?

From that markup alone, it is very hard to answer any of those questions. Using a naming convention, however, changes all that:

Now we can clearly see which classes are and are not related to each other, and how; we know what classes we can’t use outside of the scope of this component; and we know which classes we may be free to reuse elsewhere.

JavaScript Hooks

As a rule, it is unwise to bind your CSS and your JS onto the same class in your HTML. This is because doing so means you can’t have (or remove) one without (removing) the other. It is much cleaner, much more transparent, and much more maintainable to bind your JS onto specific classes.

I have known occasions before when trying to refactor some CSS has unwittingly removed JS functionality because the two were tied to each other—it was impossible to have one without the other.

Typically, these are classes that are prepended with js-, for example:


This means that we can have an element elsewhere which can carry with style of .btn {}, but without the behaviour of .js-btn.

data-* Attributes

A common practice is to use data-* attributes as JS hooks, but this is incorrect. data-*attributes, as per the spec, are used to store custom data private to the page or application(emphasis mine). data-* attributes are designed to store data, not be bound to.

Taking It Further

As previously mentioned, these are very simple naming conventions, and ones that don’t do much more than denote three distinct groups of class.

I would encourage you to read up on and further look in to your naming convention in order to provide more functionality—I know it’s something I’m keen to research and investigate further.

Further Reading

Continue Reading – Part 3