Skip to content

Instantly share code, notes, and snippets.

@fredrivett
Created June 25, 2015 16:03
Show Gist options
  • Save fredrivett/485bb3e75fb96666112b to your computer and use it in GitHub Desktop.
Save fredrivett/485bb3e75fb96666112b to your computer and use it in GitHub Desktop.
Styleguide
# Contents
* Introduction
* Syntax
* 80 Characters Wide
* Anatomy of a Ruleset
* Multi-line CSS
* Indenting
* Meaningful Whitespace
* Commenting
* High Level
* Object-Extension Pointers
* Low Level
* Naming Conventions
* Namespacing
* Hyphen Delimited
* BEM-like Naming
* Starting Context
* More Layers
* Modifying Elements
* Naming Conventions in HTML
* Javascript Hooks
* data-* Attributes
* Structure
* Settings
* Tools
* Generic
* Base
* Objects
* Components
* Utilities
* Sass Stuff
* Mixin vs. Extend
* Other Items of Note
* Foundation Settings File
# TPO CSS Styleguide
This styleguide documents the standards we're using at TPO for our front end development work. With many people having access to the codebase & being able to make changes it makes sense to document the standards we've adopted to keep everyone on the same page.
I've taken lots of cues from Harry Roberts' [CSS Guidelines](http://cssguidelin.es/) & Hugo Giraudel's [Sass Guidelines](http://sass-guidelin.es/) and some sections are almost lifted straight from them so for more in depth reasoning take a read of those.
If you have any suggestions about changes or adding to this styleguide or any general questions feel free to message me [via email](mailto:[email protected]) or on Slack -Fred
## Syntax
At a very high-level, we want:
* two (2) space indents, no tabs;
* 80 character wide columns;
* multi-line CSS;
* meaningful use of whitespace.
### 80 Characters Wide
Where possible, limit CSS files’ width to 80 characters. Reasons for this include:
* the ability to have multiple files open side by side;
* viewing CSS on sites like GitHub, or in terminal windows;
* providing a comfortable line length for comments.
```
/**
* I am a long-form comment. I describe, in detail, the CSS that follows. I am
* such a long comment that I easily break the 80 character limit, so I am
* broken across several lines.
*/
```
This is just a general rule, there will be unavoidable exceptions to this—such as URLs, or gradient syntax—which shouldn’t be worried about.
## Anatomy of a Ruleset
Before we discuss how we write out our rulesets, let’s first familiarise ourselves with the relevant terminology:
```
[selector] {
[property]: [value];
[<--declaration--->]
}
```
For example:
```
.foo, .foo--bar,
.baz {
display: block;
background-color: green;
color: red;
}
```
Here you can see we have:
* related selectors on the same line; unrelated selectors on new lines;
* a space before our opening brace ({);
* properties and values on the same line;
* a space after our property–value delimiting colon (:);
* each declaration on its own new line;
* the opening brace ({) on the same line as our last selector;
* our first declaration on a new line after our opening brace ({);
* our closing brace (}) on its own new line;
* each declaration indented by two (2) spaces;
* a trailing semi-colon (;) on our last declaration.
* This format seems to be the largely universal standard (except for variations in number of spaces, with a lot of developers preferring two (2)).
## Multi-line CSS
CSS should be written across multiple lines, except in very specific circumstances. There are a number of benefits to this:
* A reduced chance of merge conflicts, because each piece of functionality exists on its own line.
* More ‘truthful’ and reliable diffs, because one line only ever carries one change.
Exceptions to this rule should be fairly apparent, such as similar rulesets that only carry one declaration each, for example:
```
.icon {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 16px;
height: 16px;
background-image: url(/img/sprite.svg);
}
.icon--home { background-position: 0 0 ; }
.icon--person { background-position: -16px 0 ; }
.icon--files { background-position: 0 -16px; }
.icon--settings { background-position: -16px -16px; }
```
These types of ruleset benefit from being single-lined because
* they still conform to the one-reason-to-change-per-line rule;
* they share enough similarities that they don’t need to be read as thoroughly as other rulesets—there is more benefit in being able to scan their selectors, which are of more interest to us in these cases.
## Indenting
As well as indenting individual declarations, indent entire related rulesets to signal their relation to one another, for example:
```
.foo {}
.foo__bar {}
.foo__baz {}
```
By doing this, a developer can see at a glance that .foo__baz {} lives inside .foo__bar {} lives inside .foo {}.
This quasi-replication of the DOM tells developers a lot about where classes are expected to be used without them having to refer to a snippet of HTML.
N.B. Nesting in Sass should be avoided wherever possible. See the Specificity section for more details.
## Meaningful Whitespace
As well as indentation, we can provide a lot of information through liberal and judicious use of whitespace between rulesets. We use:
* One (1) empty line between closely related rulesets.
* Two (2) empty lines between loosely related rulesets.
* Five (5) empty lines between entirely new sections.
For example:
```
/**
* Foo component
*
* This is a sentence explaining the foo component
*
* 1) ...
*/
.c_foo {}
.c_foo--baz {}
.c_foo__bar {}
.c_foo__bars {}
.c_foo__bars--bar {}
```
There should never be a scenario in which two rulesets do not have an empty line between them.
## Commenting
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 `.o_btn {}` in _components.buttons.scss.
*/
.o_btn {}
```
And in your theme file:
```
/**
* These rules extend `.o_btn {}` in _objects.buttons.scss.
*/
.c_btn--positive {}
.c_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.
*/
.c_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.
# 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 we follow is: namespaced (prefixed), 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.
## Namespacing
Namespacing is the practice of prefixing our classes to clarify their type. Namespacing isn't all that common in front end development right now and can feel ugly and over the top but really comes into it's own when trying to get to grips with working out what's going on in our HTML markup.
An example of namespacing is like so:
```
<nav class="o_horizontal-list c_main-navigation" role="navigation">
<ul>
<li>...</li>
...
</ul>
</nav>
```
Here we can clearly see which classes are responsible for which styles, with an object providing the skeleton styling and a component providing the specific styling. A _huge_ benefit of this is giving the developer confidence. If this was written as `... class="horizontal-list main-navigation" ...` it may be less clear what the scope of these classes are, and where we should go to make our change.
For more info on Namespacing check out [Harry Roberts' article](http://csswizardry.com/2015/03/more-transparent-ui-code-with-namespaces/).
## Hyphen Delimited
All strings in classes are delimited with a hyphen (-), like so:
```
.o_bg-video {}
.c_follow-box {}
```
Camel case and underscores are not used for regular classes; the following are incorrect:
```
.o_bg_video {}
.c_followBox {}
```
## 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.
### 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:
```
<div class="box profile pro-user">
<img class="avatar image" />
<p class="bio">...</p>
</div>
```
How are the classes box and profile related to each other? How are the classes profile and avatar related to each other? Are they related at all? Should you be using pro-user alongside 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:
```
<div class="o_box c_profile c_profile--is-pro-user">
<img class="c_avatar c_profile__image" />
<p class="c_profile__bio">...</p>
</div>
```
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.
There are occasions when refactoring CSS can lead to removing JS functionality because the two are tied to each other—it's impossible to have one without the other.
Following from our Namespacing, these are classes that are prepended with js_, for example:
```
<input type="submit" class="o_btn js_btn" value="Follow" />
```
This means that we can have an element elsewhere which can carry with style of `.o_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. `data-*` attributes are designed to store data, not be bound to.
# Structure
Structuring our CSS correctly is vital. The benefits of structuring include easily maintainable code, a 'flat' specificity graph & consistency despite numerous developers working on the same codebase.
The structure we use at TPO is based off of Harry Roberts' [ITCSS](https://www.youtube.com/watch?v=1OKZOV-iLj4) (Inverted Triangle CSS). The basic premise of ITCSS is that the far reaching, generic selectors should be declared first, with the more specific styles being declared later and later.
```
--------------------------
\ ------Settings------ / < Globally available settings (Config switches / Brand colours)
\ -------Tools------ / < Globally available tools (Public mixins / Helper functions)
\ -----Generic---- / < Ground zero styles (low spec, far reaching (resets, *))
\ -----Base----- / < Unclassed elements (h1-h6, a, li)
\ --Objects--- / < OOCSS. Design Patterns. No cosmetics. Class exclusively now. e.g. .ui-list {}
\ Components / < Designed UI. Classes only. More explicitly named e.g. .products-list {}
\ --Util-- / < Utilities, overriders, helpers. Does a very specific job that you has declaration(s) you always want to win. !important
\ ------ /
\ ---- /
\ -- /
\ /
\/
```
## Settings
These are the foundational settings that include our site-wide variables. E.g. `$tpo-brand-color`, `$tpo-default-spacing`. These site wide TPO specific variables are prefixed with `$tpo-` to make it absolutely clear where these variables are set and so as to not conflict with the Foundation variables.
We also have Foundations settings here too.
## Tools
These are the mixins and helper functions. In order to keep our code DRY (Don't Repeat Yourself) and easy to read, code snippets that do a specific job and are used more than once are
## Generic
Currently we do not have any generic selectors as Foundation handles this for us. If we move away from Foundation or replace/tweak their resets this will be the place for this.
## Base
The base layer contains our foundational selectors. This is our unclassed elements (e.g. h1, a, p) which set 'if no classes are applied these elements should look like X' and our Foundation specific overrides for styles we can't achieve by tweaking Foundations settings.
## Objects
Objects are our building blocks, the re-usable patterns that exist throughout the site. The poster child of OOCSS (Object Oriented CSS) is Nicole Sullivan's [Media Object](http://www.stubbornella.org/content/2010/06/25/the-media-object-saves-hundreds-of-lines-of-code/). The key question to ask when deciding whether the code should be an object or a component is whether this code is tied to specific component (e.g. the main navigation) or whether it's a re-usable object (e.g. a horizontal list).
In general objects rarely change after creation. It's ok to change an existing object but you have to really consider whether this is a change that makes sense for all instances of this object. If not, then it may make sense to add a modifier (e.g. `o_undercover-list--tight`) or if it makes more drastic changes tied to a specific instance then these should be added as a component class.
## Components
As mentioned above, components are specific sections of our DOM. A component may be used multiple times throughout the website (e.g. a sign up form or the main footer) but is a lot more specific than objects as it is intended for one use case only.
Very often we will apply an object styling and then apply a components styling on top of that, with the object providing the skeleton structure and the component applying the component specific styles.
In our code we may have something like this:
```
<nav class="o_horizontal-list c_main-navigation" role="navigation">
<ul>
<li>...</li>
...
</ul>
</nav>
```
## Utilities
Utilities do a specific job. When a utility is applied it's because _no matter what other classes or styles are applied to this element, we want this utility class to win_. We may need to specify that this h1 should be in our brand color. It may not make any sense to tie this to all h1's or an object or component. In these sort of situations we want to apply a style to a specific element.
This is where utilities come in. Rather than using inline styling (a big no no) we create a utility that does that specific job. For example for applying the brand color, we have:
```
.u_color-brand { color: $tpo-brand-color !important; }
```
# Sass Stuff
## Mixin vs. Extend
This debate has gone on for a while, mainly with the implications of whether mixins provided unnecessary CSS bloat.
I'll write more on this soon, but simply put in 99% of cases we'll want to use mixins. Extends have their place but require greater care implementing (comments) and can do unexpected things.
***
## Other items of note
### Foundation Settings File
A minor note when editing Foundation settings, a standard I've adopted is to comment out the default settings. This isn't a big deal but I've found it useful in the future to know what the default or most recent setting was so I can revert if needed.
```
$paragraph-line-height: 1.6; //1.8
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment