- Introduction
- Rules (anatomy and syntax)
- Whitespace
- Indenting
- Comments
- Naming conventions
- Selectors
- Specificity
- Media queries
- Element queries
The CSS style guide in use at Procurios is based on various sources and experiences in the past few years. Most of it is based on our own experience with maintaining a large codebase. An important source of inspiration is Harry Roberts, who published his work on cssguidelin.es. Another inspiring source is Code Guide, by @mdo.
A CSS rule is built out of the following blocks:
[selector] {
[property]: [value];
}The combination of a property and a value is called a declaration.
A typical CSS rule based on this style guide looks as following:
.fooBlock {
display: block;
text-align: center;
color: #fff;
}MDN has a great piece on CSS syntax if you want to learn more.
/** bad */
.fooBlock, .fooBlock--collapsed {
display: block;
}
/** good */
.fooBlock,
.fooBlock--collapsed {
display: block;
}/** bad */
.fooBlock {
display:
block;
}
/** good */
.fooBlock {
display: block;
}/** bad */
.fooBlock {
display: block; float: left;
}
/** good */
.fooBlock {
display: block;
float: left;
}/** bad */
.fooBlock {
display: block;
float: left
}
/** good */
.fooBlock {
display: block;
float: left;
}Related property declarations should be grouped together following the order:
- Positioning
- Box model
- Typographic
- Visual
Positioning comes first because it can remove an element from the normal flow of the document and override box model related styles. The box model comes next as it dictates dimensions and placement.
Everything else takes place inside the component or without impacting the previous two sections, and thus they come last.
For example:
.declarationOrder {
position: absolute;
top: 100px;
left: 100px;
width: 1000px;
height: 1000px;
border: 1px solid #e5e5e5;
font: normal 13px "Helvetica Neue", sans-serif;
color: #444;
background: #f5f5f5;
}/** bad */
.fooBlock {
∙∙∙∙display: block;
}
.fooBlock {
∙∙display: block;
}
/** good */
.fooBlock {
display: block;
}/** bad */
.fooBlock{
display: block;
}
/** good */
.fooBlock {
display: block;
}/** bad */
.fooBlock { display: block; }
.fooBlock {
display: block; }
/** good */
.fooBlock {
display: block;
}/** bad */
.fooBlock {
display:block;
}
/** good */
.fooBlock {
display: block;
}/** bad */
.fooBlock { display: block;
color: #fff;
}
/** good */
.fooBlock {
display: block;
color: #fff;
}Where possible, limit the length of a line to a maximum of 80 characters.
/**
* This comment explains the following CSS in detail. A little bit too many
* details, because the length of one line exceeds 120 characters. Hence,
* it's carefully broken into pieces to enhance readability.
*/There will be unavoidable exceptions to this rule (fe. URLs, gradient syntax).
Our naming convention should be sufficient to visualize relations between rules.
/** bad */
.fooBlock {
...
}
.fooBlock__bar {
...
}
.fooBlock__baz {
}
/** good */
.fooBlock {
...
}
.fooBlock__bar {
...
}
.fooBlock__baz {
}We use comments in our CSS files for two purposes:
- Clarification of the document structure.
- Explanation of a specific declaration or rule.
There is no such thing as too much commenting -- as long as a comment improves the maintainability or interchangeability.
/**
* This is a sectioning comment and it explains the following CSS in detail.
*/Please note that multiple sections in 1 file might be a code smell. Consider splitting discrete chunks of code into their own files.
/** This rule needs some explanation */
.fooBlock {
overflow: hidden;
}
.fooBlock {
overflow: hidden; /** This declaration needs some explanation */
}We use a BEM like naming convention. Read more about it in our HTML style guide repository.
I quote Harry Roberts:
Poor Selector Intent is one of the biggest reasons for headaches on CSS projects. Writing rules that are far too greedy—and that apply very specific treatments via very far reaching selectors—causes unexpected side effects and leads to very tangled stylesheets, with selectors overstepping their intentions and impacting and interfering with otherwise unrelated rulesets.
CSS cannot be encapsulated, it is inherently leaky, but we can mitigate some of these effects by not writing such globally-operating selectors: your selectors should be as explicit and well reasoned as your reason for wanting to select something.
The reasoning behind using only classnames to select elements:
- The CSS is decoupled: The dependency on the DOM is kept to a minimum. It makes it much easier to move your CSS around.
- The CSS is isolated: Classnames based on our naming convention are scoped to a specific
Block. - The CSS is descriptive: Classnames based on our naming convention tell developers stuff about what they're styling.
/** bad */
header ul {
...
}
/** good */
.siteNav {
...
}Don't worry about unavoidable exceptions to this rule. Use it as a rule of thumb.
Reusable classes can be moved, recycled and duplicated across projects. They are location independent and can be reused an infinite amount of times.
/** bad */
.metaData dd {
font-weight: bold;
}
/** good */
.metaData__label {
font-weight: bold;
}Performance of CSS selectors is more interesting than it is important with today's browsers. If you want to know more about selector parsing, start here.
Not only are IDs non-reusable, they are also vastly more specific than any other selector, and therefore become specificity anomalies. Learn more about using IDs in CSS selectors here.
/** bad */
.fooBlock > .fooBlock__title {
color: hotpink;
}
/** good */
.fooBlock__title {
color: hotpink;
}The general rule is that !important is always a bad thing, but all rules have exceptions. Harry Roberts makes a distinction between proactive and reactive use of !important.
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;
}Reactive use of !important is when it is used to combat specificity problems. In these situations, it is preferable that you investigate and refactor any offending rulesets to try and bring specificity down across the board.
Place media queries as close to their relevant rule sets whenever possible. Don't bundle them all in a separate stylesheet or at the end of the document. Doing so only makes it easier for folks to miss them in the future. Here's a typical setup.
.fooBlock {
...
}
.fooBlock__avatar {
...
}
@media (min-width: 480px) {
.fooBlock {
...
}
.fooBlock__avatar {
...
}
}An "element query" is like a media query, specifically the 'width'/'height' MQs, but they apply to the width/height of the element's container, rather than of the screen or device.
EQs aren't implemented by browsers yet. Tab Atkins explains why EQs are difficult:
Element Queries (let's just call them EQs from here on) suffer from basic circular dependency issues.
Here's a simple example: Say you have an element which is normally 100px wide, but it uses an EQ to say that if its container is less than 200px wide, the element becomes 500px wide. (This is silly, but bear with me.) If the container is explicitly sized (width: 150px; or the like), this is fine, but if the container is sized to its contents (float: left;, for example), then you have a circular dependency: if the element is 100px wide, the container is 100px wide, but that trigger the EQ, so the element is 500px wide, so the container is 500px wide, but that disables the EQ, so the element is 100px wide, so the container is 100px wide, etc.
In other words: browser vendors have some fundamental issues to deal with before EQs are shipped as native feature. Tab ends his overview with:
So that's the state of Element Queries in 2014. We're not really any closer to having them than we were in 2013, but there's light on the horizon for a possible good solution.
That said, EQs are essential to create independant, isolated UI components. We've published a Javascript library that takes care of this issue. The library contains a comprehensive README with clear instructions on how to implement and use element queries.