Front-end Code Styleguide
- inspired by http://mdo.github.io/code-guide/ and https://github.com/airbnb/css
The Golden Rule
Every line of code should appear to be written by a single person, no matter the number of contributors.
HTML
Syntax
- Use soft tabs with two spaces—they're the only way to guarantee code renders the same in any environment. This should be handled by the editor configuration file.
- Nested elements should be indented once (two spaces).
- Always use double quotes, never single quotes, on attributes.
- Don't include a trailing slash in self-closing elements—the HTML5 spec says they're optional.
- Don’t omit optional closing tags (e.g.
</li>or</body>).
<!DOCTYPE html>
<html>
<head>
<title>Page title</title>
</head>
<body>
<img src="images/company-logo.png" alt="Company">
<h1 class="hello-world">Hello, world!</h1>
</body>
</html>
Practicality over purity
Strive to maintain HTML standards and semantics, but not at the expense of practicality. Use the least amount of markup with the fewest intricacies whenever possible.
Attribute order
HTML attributes should come in this particular order for easier reading of code.
- class
- id, name
- data-*
- src, for, type, href, value
- title, alt
- role, aria-*
- Classes make for great reusable components, so they come first. Ids are more specific and should be used sparingly (e.g., for in-page bookmarks), so they come second.
<a class="..." id="..." data-toggle="modal" href="#">
Example link
</a>
<input class="form-control" type="text">
<img src="..." alt="...">
Reducing markup
Whenever possible, avoid superfluous parent elements when writing HTML. Many times this requires iteration and refactoring, but produces less HTML. Take the following example:
<!-- Not so great -->
<span class="avatar">
<img src="...">
</span>
<!-- Better -->
<img class="avatar" src="...">
JavaScript generated markup
Writing markup in a JavaScript file makes the content harder to find, harder to edit, and less performant. Avoid it whenever possible.
Our current codebase has obviously broken this rule over and over, with complex dom structures defined in the javascript layer - while there are strategies for making this practice less obhorrent, in the long-run it's best avoided alltogether
CSS / Sass
Terminology
Rule declaration
A “rule declaration” is the name given to a selector (or a group of selectors) with an accompanying group of properties. Here's an example:
.listing {
font-size: 18px;
line-height: 1.2;
}
Selectors
In a rule declaration, “selectors” are the bits that determine which elements in the DOM tree will be styled by the defined properties. Selectors can match HTML elements, as well as an element's class, ID, or any of its attributes. Here are some examples of selectors:
.my-element-class {
/* ... */
}
[aria-hidden] {
/* ... */
}
Properties
Finally, properties are what give the selected elements of a rule declaration their style. Properties are key-value pairs, and a rule declaration can contain one or more property declarations. Property declarations look like this:
/* some selector */ {
background: #f1f1f1;
color: #333;
}
CSS
Formatting
- Use soft tabs (2 spaces) for indentation
- Prefer dashes over camelCasing in class names. Underscores are OK if you're using BEM (see OOCSS and BEM below).
- Do not use ID selectors
- When using multiple selectors in a rule declaration, give each selector its own line.
- Put a space before the opening brace
{in rule declarations - In properties, put a space after, but not before, the
:character. - Put closing braces
}of rule declarations on a new line - Put blank lines between rule declarations
Bad
.avatar{
border-radius:50%;
border:2px solid white; }
.no, .nope, .not_good {
// ...
}
#lol-no {
// ...
}
Good
.avatar {
border-radius: 50%;
border: 2px solid white;
}
.one,
.selector,
.per-line {
// ...
}
Rules - Declaration order
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 a component's dimensions and placement.
Everything else takes place inside the component or without impacting the previous two sections, and thus they come last.
.declaration-order {
/* Positioning */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
/* Box-model */
display: block;
float: right;
width: 100px;
height: 100px;
/* Typography */
font: normal 13px "Helvetica Neue", sans-serif;
line-height: 1.5;
color: #333;
text-align: center;
/* Visual */
background-color: #f5f5f5;
border: 1px solid #e5e5e5;
border-radius: 3px;
/* Misc */
opacity: 1;
}
Comments
- Prefer line comments (
//in Sass-land) to block comments. - Prefer comments on their own line. Avoid end-of-line comments.
- Write detailed comments for code that isn't self-documenting:
- Uses of z-index
- Compatibility or browser-specific hacks
Selectors
- Use classes over generic element tag for optimum rendering performance.
- Avoid using several attribute selectors (e.g., [class^="..."]) on commonly occuring components. Browser performance is known to be impacted by these.
- Keep selectors short and strive to limit the number of elements in each selector to three.
- Scope classes to the closest parent only when necessary (e.g., when not using prefixed classes).
- Additional reading:
Scope CSS classes with prefixes Stop the cascade
Class names
- Keep classes lowercase and use dashes (not underscores or camelCase). Dashes serve as natural breaks in related class (e.g., .btn and .btn-danger).
- Avoid excessive and arbitrary shorthand notation. .btn is useful for button, but .s doesn't mean anything.
- Keep classes as short and succinct as possible.
- Use meaningful names; use structural or purposeful names over presentational.
- Prefix classes based on the closest parent or base class.
- Use .js-* classes to denote behavior (as opposed to style), but keep these classes out of your CSS.
It's also useful to apply many of these same rules when creating Sass and Less variable names.
OOCSS and BEM
We encourage some combination of OOCSS and BEM for these reasons:
- It helps create clear, strict relationships between CSS and HTML
- It helps us create reusable, composable components
- It allows for less nesting and lower specificity
- It helps in building scalable stylesheets
OOCSS, or “Object Oriented CSS”, is an approach for writing CSS that encourages you to think about your stylesheets as a collection of “objects”: reusuable, repeatable snippets that can be used independently throughout a website.
- Nicole Sullivan's OOCSS wiki
- Smashing Magazine's Introduction to OOCSS
BEM, or “Block-Element-Modifier”, is a naming convention for classes in HTML and CSS. It was originally developed by Yandex with large codebases and scalability in mind, and can serve as a solid set of guidelines for implementing OOCSS.
- CSS Trick's BEM 101
- Harry Roberts' introduction to BEM
Example
<article class="listing-card listing-card--featured">
<h1 class="listing-card__title">Adorable 2BR in the sunny Mission</h1>
<div class="listing-card__content">
<p>Vestibulum id ligula porta felis euismod semper.</p>
</div>
</article>
.listing-card { }
.listing-card--featured { }
.listing-card__title { }
.listing-card__content { }
.listing-cardis the “block” and represents the higher-level component.listing-card__titleis an “element” and represents a descendant of.listing-cardthat helps compose the block as a whole..listing-card--featuredis a “modifier” and represents a different state or variation on the.listing-cardblock.
Selectors - IDs
While it is possible to select elements by ID in CSS, it should generally be considered an anti-pattern. ID selectors introduce an unnecessarily high level of specificity to your rule declarations, and they are not reusable.
For more on this subject, read CSS Wizardry's article on dealing with specificity.
Selectors - JavaScript hooks
Avoid binding to the same class in both your CSS and JavaScript. Conflating the two often leads to, at a minimum, time wasted during refactoring when a developer must cross-reference each class they are changing, and at its worst, developers being afraid to make changes for fear of breaking functionality.
We recommend creating JavaScript-specific classes to bind to, prefixed with .js-:
on sho.com we did something a little different. We used data attributes to trigger javascript functionality, where the attribute value is the name of the component or the behavior... but the princible is the same
<div class="toggle-thang" data-component="sho.ui.Toggle">...</div>
<!-- or this -->
<a href="#" data-behavior="track-click">Order</a>
<button class="btn btn-primary js-request-to-book">Request to Book</button>
Sass
Syntax
- Use the
.scsssyntax, never the original.sasssyntax - Order your
@extend, regular CSS and@includedeclarations logically (see below)
Ordering of property declarations
@extenddeclarationsJust as in other OOP languages, it's helpful to know right away that this “class” inherits from another.
.btn-green { @extend btn; // ... }Property declarations
Now list all standard property declarations, anything that isn't an
@extend,@include, or a nested selector..btn-green { @extend btn; background: green; font-weight: bold; // ... }@includedeclarationsGrouping
@includes at the end makes it easier to read the entire selector, and it also visually separates them from@extends..btn-green { @extend btn; background: green; font-weight: bold; @include transition(background 0.5s ease); // ... }Nested selectors
Nested selectors, if necessary, go last, and nothing goes after them. Add whitespace between your rule declarations and nested selectors, as well as between adjacent nested selectors. Apply the same guidelines as above to your nested selectors.
.btn { @extend btn; background: green; font-weight: bold; @include transition(background 0.5s ease); .icon { margin-right: 10px; } }
Mixins
Mixins, defined via @mixin and called with @include, should be used sparingly and only when function arguments are necessary. A mixin without function arguments (i.e. @mixin hide { display: none; }) is better accomplished using a placeholder selector (see below) in order to prevent code duplication.
Placeholders
Placeholders in Sass, defined via %selector and used with @extend, are a way of defining rule declarations that aren't automatically output in your compiled stylesheet. Instead, other selectors “inherit” from the placeholder, and the relevant selectors are copied to the point in the stylesheet where the placeholder is defined. This is best illustrated with the example below.
Placeholders are powerful but easy to abuse, especially when combined with nested selectors. As a rule of thumb, avoid creating placeholders with nested rule declarations, or calling @extend inside nested selectors. Placeholders are great for simple inheritance, but can easily result in the accidental creation of additional selectors without paying close attention to how and where they are used.
Sass
// Unless we call `@extend %icon` these properties won't be compiled!
%icon {
font-family: "Airglyphs";
}
.icon-error {
@include icon;
color: red;
}
.icon-success {
@include icon;
color: green;
}
CSS
.icon-error,
.icon-success {
font-family: "Airglyphs";
}
.icon-error {
color: red;
}
.icon-success {
color: green;
}
Nested selectors
Do not nest selectors more than three levels deep!
.page-container {
.content {
.profile {
// STOP!
}
}
}
When selectors become this long, you're likely writing CSS that is:
- Strongly coupled to the HTML (fragile) —OR—
- Overly specific (powerful) —OR—
- Not reusable
Again: never nest ID selectors!
If you must use an ID selector in the first place (and you should really try not to), they should never be nested. If you find yourself doing this, you need to revisit your markup, or figure out why such strong specificity is needed. If you are writing well formed HTML and CSS, you should never need to do this.
JavaScript
Naming conventions
javascript class:
CamelCaseWithUpperCaseLeadingChar
'MoviesCarousel', 'Accordian', 'HomepageCarousel'
javascript class in package:
lowercase.dot.delimminated.path.to.CamelCaseClassName
'sho.ui.Carousel','sho.ui.movies.Carousel'
javascript filename:
lowercasecasewithnospaces.js
moviescarousel.js, accordian.js homepagecarousel.js
// (I would have used lowercase_with_underscores, but this is a limitation of the sho.require() implementation).
javascript bootstrapper for project/page:
/path/to/project/or/page..../js/index.js (put your kickoff code and dom:ready event in here)