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:
- Object-oriented CSS
- BEM
- Suit CSS naming conventions
- Harry Robert's CSS styleguide is quite a rant, and probably too in-depth for the casual CSS-writer, but the sections on CSS selectors and Architecture are particularly lucid.
- How browsers work very long and very thorough technical discussion of what it takes to render a webpage. The CSS section in particular is really helpful.
Where to write CSS styles
In .less and .css files only. Exceptions can be made for instance when you need to define an explicit height or width for an image; then it makes sense to do style="height:100px;"
, or when you need to hide an element using the display
property, then use show()
with jQuery to show 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 rules are the defaults. They are almost exclusively single element selectors but it could include attribute selectors, pseudo-class selectors, child selectors or sibling selectors. Essentially, a base style says that by default, wherever this element is on the page, it should look like this.
- Layout rules divide the page into sections. Layouts hold one or more modules together.
- Modules are the reusable, modular parts of our design. They are the callouts, the sidebar sections, the product lists and so on.
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 #id
s 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
- When declaring your styles, use the least number of selectors required to style an element. In most cases this should be either one or two.
- Use class names that are as short as possible but as long as necessary, in order to keep specificity down; readability and performance up.
- Select what you want explicitly, rather than relying on circumstance or coincidence. Good selector intent will rein in the reach and leak of your styles.
- Write selectors for reuse, so that you can work more efficiently and reduce waste and repetition.
- Do not nest selectors unnecessarily, because this will increase specificity and affect where else you can use your styles. In most cases, nesting should only be one level deep, meaning that most selectors should only have two classes. Any deeper nesting will hurt both readability and performance.
- Refrain from using over-qualified selectors,
div.container
can simply be stated as.container
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:
- It should never be done at the page level.
- Instead it be done at the component/module level.
- Furthermore, it should be done descriptively and functionally,
- and never based on location.
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.
Google Tag Manager
Syntax: gtm-<descriptiveThing>
When adding classes to elements for tracking with Google Tag Manager, use the gtm
prefix so others know its purpose.
As above, these classes should never 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.
- Colors
- Font families, faces, and weights
- Font sizes
- z-Indices
- Line height
- Letter spacing
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.
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-6
s splits the grid-12
in half, and the second level of grid-6
s 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:
- The
text-align
property will left-, right-, or center align both traditional text elements as well asinline-block
elements. Leaving out css classes forfloat
decreases the size of our css files and the number of classes the layout engine needs to check against. vertical-align: middle
works!!! Just add thelayout_centerVertical
class to all the sibling elements you want aligned (to each other). Make sure they havedisplay: inline-block
or add thelayout_inlineBlock
class if not.
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-6
s 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.
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:
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>
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>
tl;dr:
- 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 thegrid-parent
class. If that doesn't work double-check that their total isn't more than 12. - 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 agrid-*
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.
Formatting
The following are some high level page formatting style rules that aren't enforced strictly.
Spacing
- Put spaces after
:
in property declarations. - Put spaces before
{
in rule declarations. - Place closing braces of declaration blocks on a new line.
- Rule declarations live on their own lines
- Property declarations live on their own line
- For rules with multiple values, separate each value with a single space following the comma(s). For Example:
font-family: Helvetica, sans-serif;
- Rulesets are separated by a 2 new lines (i.e. an empty line between each ruleset). not 0, not 1, not 3.
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;
}
- Selectors should be separated by 1 new line, not 0, not 2, not 3. Right:
.multiple,
.classes,
.get-new-lines {
display: block;
}
Wrong:
.multiple,
.classes,
.get-new-lines {
display: block;
}
Also wrong:
.multiple, .classes, .get-new-lines {
display: block;
}
- Similar rulesets that only carry one declaration each can be an exception to the above rules, if the result is clear and concise:
.icon-home { background-position: 0 0 ; }
.icon-person { background-position: -16px 0 ; }
.icon-files { background-position: 0 -16px; }
.icon-settings { background-position: -16px -16px; }
- Long, comma-separated property values - such as collections of gradients or shadows - can be arranged across multiple lines in an effort to improve readability and produce more useful diffs. There are various formats that could be used; one example is shown below.
.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
- Know when to use the
height
property. It should be used when you are including outside elements (such as images). Otherwise useline-height
for more flexibility. - Unitless
line-height
is preferred because it does not inherit a percentage value of its parent element, but instead is based on a multiplier of the font-size.