GlobalGiving CSS Style Guide

Meta information

Language

This document is written in GitHub Flavored Markdown

Sources

Based on the following excellent style guides:

Background reading

A few articles that explain the intent and context of this document:

Where to write CSS styles

In .less and .css files only. The only exception I've found is when you need to define an explicit height or width for an image; then it makes sense to do style="height:100px;" but it also makes me cringe a little every time I do it. There's no reason to ever add <style> blocks to your page's head.

Break your page down into reusable components (see below), then create a .less file in the modules folder for each one (remember to aim for reusability!). @import each file in the relevant section of the styleguide (e.g. 04_small_decorations.less). When building your page, make a less file in the location of your choosing (possibly www/your-page-name) and use @import statements to include all the modules your page needs.

Base, Layout, Module

I found the Scalable and Modular Architecture for CSS book useful in defining the different applications of CSS. For our purposes, Base, Layout, and Module will be most applicable (also perhaps State, which can be considered part of Module).

Here's the quick overview:

Base and Layout styles should mostly be unchanged, or changed with great care.

This document mostly focuses on the Module part (also called components), because those are the parts that change most often, and that most members of the team will contribute to.

Class naming conventions

id vs. class

The community seems split on this, and there are good arguments both for and against using #ids in CSS, but I was most swayed by this point, from Harry Robert:

If we want to keep specificity low, which we do, we have one really quick-win, simple, easy-to-follow rule that we can employ to help us: avoid using IDs in CSS. Not only are IDs inherently non-reusable, they are also vastly more specific than any other selector, and therefore become specificity anomalies. Where the rest of your selectors are relatively low specificity, your ID-based selectors are, comparatively, much, much higher.

Don't use IDs in your CSS selectors (though they're still fine for javascript hooks, anchor links, etc.)

Class names and selector usage best practices

If we're basing all our styling on classes, then we need some standards to keep everything in order. Now let's get into the heart of our naming conventions:

Components

Syntax: <componentName>[_<modifierName>|-<descendantName>]

By defining a top-level name for all our components, we greatly reduce the risk of rule collisions caused by the fact that the class attribute is one global namespace. Some rules for namespacing your components:

This allows us to create reusable components that can be put into other contexts and extended. See the Architecture section of Harry Robert's guidelines for how OO, SRP, Open/Closed Principle, DRY, Composition over Inheritance, and SoC apply to how we name and select HTML elements.

Because we use single hyphens where others use double-hyphens, the names of all components, modifiers, and subcomponents must be written in camelCase. Also you may not create a component named js, or a handful of other names protected for the uses detailed below.

The one exception to this naming scheme is the grid- classes which use hyphens where they should use underscores. This is to avoid naming collisions with the old grid_ styles from 960.css. If anyone is bothered enough by this, or if we successfully get away from using 960, feel free to change this.

componentName

The highest level of module organization. This is the top-level element which contains all the subcomponents making up the module. Give it a name descriptive of its functional attributes, not its location or the specific use-case on the page you're building. Think whiteBoxedList instead of valueOutcomes. Think about someone reusing it in another context.

.myComponent { /* … */ }

<article class="myComponent">
  …
</article>

componentName-descendantName

A component descendant is a class that is attached to a descendant node of a component. It's responsible for applying presentation directly to the descendant on behalf of a particular component.

<article class="tweet">
  <header class="tweet-header">
    <img class="tweet-avatar" src="[...]" alt="[...]">
    …
  </header>
  <div class="tweet-body">
    …
  </div>
</article>

componentName_modifierName

A component modifier is a class that modifies the presentation of the base component in some form. Modifier names must be separated from the component name by an underscore.

/* Core button */
.btn { /* … */ }
/* Primary button style */
.btn_ggPrimary { /* … */ }

<a class="btn btn_ggPrimary">…</a>

Javascript

Syntax: js-<targetName>

JavaScript-specific classes reduce the risk that changing the structure or theme of components will inadvertently affect any required JavaScript behavior. It is not necessary to use them in every case, but if you are creating a class which you intend to use as a Javascript selector and not for styling, you should probably be adding the js- prefix.

In practice this looks like this:

<a href="/login" class="btn btn-primary js-login"></a>

Javascript-specific classes should not, under any circumstances, be styled.

Variables

When I learned Excel, I was taught never to hard-code a constant into a formula. If we had a weekly total and wanted to calculate a daily average, never divide by 7, instead put the value 7 into a cell and label it "days in a week". This way, nobody will be confused at what that 7 represents, and we can update a constant's value in one place and have all references to it update.

Similarly, we put as many constants into shared LESS files as possible, so that we can update our visual styles more easily.

Currently, the constants below are being aggregated. This means that if you want to make reference to one of these properties, put @import (reference) "../constants/base.less" at the top of your file, then use the appropriate @mixin instead of hardcoding the value directly. See the constants folder for the definitions of these scales.

Polyfills

The polyfill.less file contains less macros which are not compiled into production code, but are used to provide polyfill support for nonstandard properties. By centralizing everything in this way, we can update it as browser support standardizes and improves.

Comments

Each module must be accompanied by a KSS comment. In most cases this just consists of a title, short explanation, and a block of html markup to display the module and any modifiers (if applicable). This type of documentation functions as a sort of test harness, forcing us to show that the css functions as expected and does not rely on any external styling or side-effects. It also helps us focus on the goal of modular and reusable components. Finally, by flipping through the pages of this styleguide, we can test any changes to the base css styles.

Read more about kss

Getting Used to v2/css

This section explains some things that are different in the current stack from the previous one, and contains some tips and best practices for getting used to the new css environment.

border-box ftw

All elements get box-sizing: border-box by default. This allows you to add padding without changing the element's computed width. It's just better that way, and imho should be the default. Not sure what more to say.

Grids are simpler

With the goal of doing one thing and doing it well, the grid system we're using (pure) just splits up the space it's given into columns with the relative widths you define. Before, they had gutters built in, and classes like alpha and omega to specify placement. This made things really complicated, so now those things are separate. We're still using a 12-column grid, so, for instance grid-6 does only one thing: it applies width: 50%.

One other minor change is that grids are no longer absolute widths; they're applied as percentages, so their width is relative to their location.

For instance, in 960, markup like

<div class="grid_12">
   <div class="grid_6">
      <div class="grid_3">1</div>
      <div class="grid_3">2</div>
   </div>
   <div class="grid_6">
      <div class="grid_3">3</div>
      <div class="grid_3">4</div>
   </div>
</div>

would have produced four columns, each roughly 240px wide (960/4). In fact anywhere you defined a grid_3 you'd end up with a 240px column. The corresponding layout in pure would be coded as

<!-- DON'T USE THIS. SEE THE inline-block SECTION FOR WHAT'S MISSING -->
<div class="grid-12">
   <div class="grid-6">
      <div class="grid-6">1</div>
      <div class="grid-6">2</div>
   </div>
   <div class="grid-6">
      <div class="grid-6">3</div>
      <div class="grid-6">4</div>
   </div>
</div>

The first level of grid-6s splits the grid-12 in half, and the second level of grid-6s splits those halves in half, leaving us in the same place: four quarter-width columns. So at every level of layout, consider yourself as being in a grid-12 environment. You can cut it up as many times as you want, and further grid classes will still divide the space respective to the ratio of their value to the magic number 12.

Responsive grids

Along with grid classes you're used to like grid-1, grid-3, and grid-7, pure comes with classes to define an object's size at different device sizes. The details are a bit more complicated (see the table in section 2 for more detail), but for our purposes just be aware that there are classes like grid-md-6 and grid-lg-6 which will make the styled element take up 6 columns (50%) on tablets and desktops, respectively. Note that the classes are "mobile-first", so any screens larger than the defined ones will have the defined properties.

For example, a class list like grid-12 grid-md-6 grid-lg-3 says to take up all the horizontal space on small screens, half the space on medium (tablet-ish) screens, and a quarter on large (desktop-ish) ones.

A list like grid-12 grid-md-6 still says to take up all the space on small screens, and half on tablets, desktops, and anything larger.

inline-block all the things

Pure positions the grid elements by defining them as display: inline-block, whereas 960.css used the float property. Therefore we're using inline-block much more heavily than before, and float (hopefully) not at all. inline-block has some benefits:

There is, however, at least one drawback: inline-block elements by default have horizontal space between them when there's whitespace between them in the markup. This is because inline-block elements are (sort of) treated like words in a paragraph of text. You'd expect spaces between words, and the words to wrap at the end of a line of text, which is pretty much what's happening here.

For example, if you created this simple markup:

<div class="grid-12">
   <div class="grid-6 col_ggPrimary1">1</div>
   <div class="grid-6 col_ggPrimary2">2</div>
</div>

each of the grid-6s take up 50% of the parent div, but the browser will also insert a few pixels of space between them, so the width of these two divs plus the space between is more than 100% of the width of the parent! inline-block elements wrap instead of overflowing, so since it can't all fit, the two divs are positioned one above the other instead of side-by-side as you probably intended.

1
2

Fortunately the fix is easy: pure comes with a class called grid-parent which does some magic with the letter-spacing property to reduce the space between adjacent divs down to zero. This markup

<div class="grid-12 grid-parent">
   <div class="grid-6 col_ggPrimary1">1</div>
   <div class="grid-6 col_ggPrimary2">2</div>
</div>

will work as expected:

1
2

However, because grid-parent reduces the letter-spacing property, grid-parent divs can only contain grid-* divs as descendants.

<div class="grid-parent">
   <div class="grid-6 col_ggPrimary1">
      This is some text that's in the right place.
   </div>
   This is some text that's in the wrong place.
</div>
This is some text that's in the right place.
This is some text that's in the wrong place.

As you can see, the grid-* divs reset the letter-spacing property so that text renders as expected within them:

<div class="grid-parent">
   <div class="grid-6 col_ggPrimary1">
      This is some text that's in the right place.
   </div>
   <div class="grid-6 col_ggPrimary2">
      Now this text is in the right place too.
   </div>
</div>
This is some text that's in the right place.
Now this text is in the right place too.

tl;dr:

  1. if you have a series of grid-* divs which are wrapping onto a second line and you think they should all fit on one line, make sure the parent has the grid-parent class. If that doesn't work double-check that their total isn't more than 12.
  2. if your text is all jumbled and the letters are all on top of each other, its parent is probably a grid-parent. Either remove that class or add another level of nesting with a grid-* class.

% and em. no px.

For a variety of good reasons--most of them centering on responsiveness--we define widths, margins, and padding using percents, and font sizes and line heights by ems. For the most part I hope this is pretty easy to pick up, but there's one case where it makes things a bit tricky. Consider this markup:

<div class="grid-12 grid-parent">
   <div class="grid-8 col_ggPrimary1">
      <div class="box_verticalMargin3 col_ggPrimary2">1</div>
   </div>
   <div class="grid-4 col_ggPrimary3">
      <div class="box_verticalMargin3 col_ggPrimary2">2</div>
   </div>
</div>
1
2

Since both inner divs have the same box_verticalMargin3 class, you might expect them to get the same size margins, but they don't because that class defines margin: 4.5%;, and by definition when margins are defined in css they're relative to the element's parent's width. Since the parent of the first blue box is twice as wide as the parent of the second blue box, its margin is twice as thick.

The easiest way I've found to get around this is to use padding and apply it to elements at the same level in the hierarchy. Because they have the same parent, the percentage of padding is the same.

<div class="grid-12 grid-parent">
   <div class="grid-8 box_verticalPadded3 col_ggPrimary1">
      <div class="col_ggPrimary2">1</div>
   </div>
   <div class="grid-4 box_verticalPadded3 col_ggPrimary3">
      <div class="col_ggPrimary2">2</div>
   </div>
</div>
1
2

Note that these paddings are thicker than the ones above because they're relative to the grid-12 element, whereas neither of the previous ones were. Plan accordingly.

Formatting

The following are some high level page formatting style rules that aren't enforced strictly.

Spacing

  font-family: Helvetica, sans-serif;

Right:

  .styleguide-format {
    color: #111;
}

  .styleguide-format2 {
    color: #000;
}

Wrong:

  .styleguide-format {
    color: #111;
  }
  .styleguide-format2 {
    color: #000;
}

Wrong:

  .styleguide-format {
    color: #111;
  } .styleguide-format2 {
    color: #000;
}
.multiple,
.classes,
.get-new-lines {
        display: block;
}

Wrong:

.multiple,

.classes,

.get-new-lines {
        display: block;
}

Also wrong:

.multiple, .classes, .get-new-lines {
    display: block;
}
.icon-home     { background-position:   0     0  ; }
.icon-person   { background-position: -16px   0  ; }
.icon-files    { background-position:   0   -16px; }
.icon-settings { background-position: -16px -16px; }
.selector {
        background-image:
            linear-gradient(#fff, #ccc),
            linear-gradient(#f3c, #4ec);
        box_shadow:
            1px 1px 1px #000,
            2px 2px 1px 1px #ccc inset;
}

Quotes

Quotes are optional in CSS and LESS. We use double quotes as it is visually clearer that the string is not a selector or a style property.

Right:

background-image: url("/img/you.jpg");
font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial;

Wrong:

background-image: url(/img/you.jpg);
font-family: Helvetica Neue Light, Helvetica Neue, Helvetica, Arial;

Units on zero values

Where allowed, avoid specifying units for zero-values, e.g.

margin: 0

not

margin: 0px

Unnecessary shorthand

Avoid unnecessary shorthand declarations.

Right:

.good {
  /** Only set the bottom margin to 20px */
  margin-bottom: 20px;
}

Wrong:

.not-so-good {
  /** sets the bottom margin to 20px, but also sets the top/left/right to 0px, be sure this is what you want? */
  margin: 0 0 20px;
}

Other

The !important keyword

Proactive use of !important is when it is used before you’ve encountered any specificity problems; when it is used as a guarantee rather than as a fix. For example:

.one-half {
    width: 50% !important;
}

.hidden {
    display: none !important;
}

These two helper, or utility, classes are very specific in their intentions: you would only use them if you wanted something to be rendered at 50% width or not rendered at all. If you didn’t want this behaviour, you would not use these classes, therefore whenever you do use them you will definitely want them to win.

Here we proactively apply !important to ensure that these styles always win. This is correct use of !important to guarantee that these trumps always work, and don’t accidentally get overridden by something else more specific.

Incorrect, reactive use of !important is when it is used to combat specificity problems after the fact: applying !important to declarations because of poorly architected CSS.

Only use !important proactively, not reactively.

Self-chaining selectors

A less all-or-nothing solution that may sometimes help (I'm still not sure how I feel about this) is to chain a selector with itself to increase its specificity. See here and check out the examples.

Height and line-height