Skip to content

Code Styleguide

0xADADA edited this page Sep 25, 2017 · 2 revisions

Style Guide

Table of contents

  1. General principles
  2. Whitespace
  3. HTML
  1. CSS
  1. JavaScript

General principles

"Part of being a good steward to a successful project is realizing that writing code for yourself is a Bad Idea™. If thousands of people are using your code, then write your code for maximum clarity, not your personal preference of how to get clever within the spec." - Idan Gazit

  • Don't try to prematurely optimize your code; keep it readable and understandable.
  • Your CSS should look the same as the rest of the CSS in the project.

Whitespace

  • Use whitespace to improve readability.
  • Use 2 spaces for indentation.
  • Don't use more than one blank line as a separator.
  • Strip all end-of-line whitespace.

HTML

Format

  • Always use lowercase tag and attribute names.
  • Write one discrete, block-level element per line.
  • Use one additional level of indentation for each nested block-level element.
  • Use valueless boolean attributes (e.g. checked rather than checked="checked").
  • Always use double quotes to quote attribute values.
  • Omit the type attributes from link stylesheet, style and script elements.
  • Always include closing tags.

(Keep line-length to a sensible maximum, e.g., 80 columns.)

Example:

<div class="Tweet">
  <a href="{{url}}">
    <img src="{{avatar}}" alt="">
  </a>
  <p>{{text}}</p>
  <button disabled>Reply</button>
</div>

Exceptions and slight deviations

Elements with multiple attributes can have attributes arranged across multiple lines in an effort to improve readability and produce more useful diffs.

Example:

<a class="{{class}}"
  data-action="{{action}}"
  data-id="{{id}}"
  href="{{url}}">
  <span>{{text}}</span>
</a>

HTML attribute order

HTML attributes should be listed in alphabetical order.

Example:

<a class="{{class}}" data-name="{{name}}" href="{{url}}" id="{{id}}">{{text}}</a>

Naming

Naming is hard, but very important. It's a crucial part of the process of developing a maintainable code base. Don't be afraid to rename components.

  • Use clear, thoughtful, and appropriate names for HTML classes. The names should be informative both within HTML and CSS files.
  • Avoid systematic use of abbreviated class names. Don't make things difficult to understand.

Example with "bad" names:

<div class="cb s-scr"></div>
.cb {
  background: #000;
}

.cb.s-scr {
  overflow: auto;
}

Example with better names:

<div class="ColumnBody is-scrollable"></div>
.ColumnBody {
  background: #000;
}

.ColumnBody.is-scrollable {
  overflow: auto;
}

HTML practical example

An example of various conventions.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Document</title>
    <link rel="stylesheet" href="main.css">
    <script src="main.js"></script>
  </head>
  <body>
    <article class="Post" id="1234">
      <time class="Post-timestamp">{{date}}</time>
      <a data-analytics-action="{{action}}"
       data-analytics-category="{{category}}"
       data-id="1234"
       href="{{url}}">{{text}}</a>
      <ul>
        <li>
          <a href="{{url}}">{{text}}</a>
          <img src="{{src}}" alt="">
        </li>
        <li>
          <a href="{{url}}">{{text}}</a>
        </li>
      </ul>

      <a class="u-linkComplex" href="{{url}}">
        <span class="u-linkComplex-target">{{text}}</span>
        {{text}}
      </a>

      <input value="text" readonly>
    </article>
  </body>
</html>

CSS

Comments

Well commented code is extremely important. Take time to describe components, how they work, their limitations, and the way they are constructed. Don't leave others in the team guessing as to the purpose of uncommon or non-obvious code.

Comment style should be simple and consistent within a single code base.

  • Place comments on a new line above their subject.
  • Keep line-length to a sensible maximum, e.g., 80 columns.
  • Make liberal use of comments to break CSS code into discrete sections.
  • Use "sentence case" comments and consistent text indentation.
  • Use numeric end-of-line comments to reference documentation for individual declarations.
  • Maintain indent level with the asterix on the first line.

Tip: configure your editor to provide you with shortcuts to output agreed-upon comment patterns.

Example:

/**
Short description using Doxygen-style comment format

The first sentence of the long description starts here and continues on this
line for a while finally concluding here at the end of this paragraph.
 
The long description is ideal for more detailed explanations and
documentation. It can include example HTML, URLs, or any other information
that is deemed necessary or useful.

TODO: This is a todo statement that describes an atomic task to be completed
at a later date. It wraps after 80 characters and following lines are
indented by 2 spaces.

@tag This is a tag named 'tag'

1. A helpful description of a declaration's purpose.
2. Another declaration or collection of declarations can reference this
   comment with an inline comment corresponding to the lists number.
*/

/* Thematic section comment block 80-char wide.
   ========================================================================== */

/* Basic one-line comment */

Format

The chosen code format ensures that code is: easy to read; easy to clearly comment; minimizes the chance of accidentally introducing errors; and results in useful diffs and blames.

  • Use one discrete selector per line in multi-selector rulesets.
  • Include a single space before the opening brace of a ruleset.
  • Include one declaration per line in a declaration block.
  • Use one level of indentation for each declaration.
  • Include a single space after the colon of a declaration.
  • Use lowercase and shorthand hex values, e.g., #aaa.
  • Use single or double quotes consistently. Preference is for double quotes, e.g., content: "".
  • Quote attribute values in selectors, e.g., input[type="checkbox"].
  • Where allowed, avoid specifying units for zero-values, e.g., margin: 0.
  • Include a space after each comma in comma-separated property or function values.
  • Include a semi-colon at the end of the last declaration in a declaration block.
  • Place the closing brace of a ruleset in the same column as the first character of the ruleset.
  • Separate each ruleset by a blank line.
.selector-1,
.selector-2,
.selector-3[type="text"] {
  background: #fff;
  background: linear-gradient(#fff, rgba(0, 0, 0, 0.8));
  box-sizing: border-box;
  color: #333;
  display: block;
  font-family: helvetica, arial, sans-serif;
}

.selector-a,
.selector-b {
  padding: 10px;
}

Declaration order

Use alphabetical ordering of declarations unless the cascade absolutely requires otherwise.

.selector {
  background: #000;
  border: 10px solid #333;
  box-sizing: border-box;
  color: #fff;
  display: inline-block;
  font-family: sans-serif;
  font-size: 16px;
  text-align: right;
  width: 100%;
}

Exceptions and slight deviations

Large blocks of single declarations can use a slightly different, single-line format. In this case, a space should be included after the opening brace and before the closing brace.

.selector-1 { width: 10%; }
.selector-2 { width: 20%; }
.selector-3 { width: 30%; }

Long, comma-separated property values - such as collections of shadows - or function arguments - such as those for gradients - can be arranged across multiple lines in an effort to improve readability and produce more useful diffs.

.selector {
  background-image:
    repeating-linear-gradient(
      -45deg,
      transparent,
      transparent 25px,
      rgba(255, 255, 255, 1) 25px,
      rgba(255, 255, 255, 1) 50px
    );
  box-shadow:
    1px 1px 1px #000,
    2px 2px 1px 1px #ccc inset;
}

For the names of classes and utilities, see CSS Naming Conventions

CSS practical example

An example of various conventions.

/** @define Grid; use strict */

/**
 * Column layout with horizontal scroll.
 *
 * This creates a single row of full-height, non-wrapping columns that can
 * be browsed horizontally within their parent.
 *
 * Example HTML:
 *
 * <div class="Grid">
 *   <div class="Grid-cell Grid-cell--3"></div>
 *   <div class="Grid-cell Grid-cell--3"></div>
 *   <div class="Grid-cell Grid-cell--3"></div>
 * </div>
 */

/**
 * Grid container
 *
 * Must only contain `.Grid-cell` children.
 *
 * 1. Remove inter-cell whitespace
 * 2. Prevent inline-block cells wrapping
 */

.Grid {
  font-size: 0; /* 1 */
  height: 100%;
  white-space: nowrap; /* 2 */
}

/**
 * Grid cells
 *
 * No explicit width by default. Extend with `.Grid-cell--n` classes.
 *
 * 1. Reset font-size inherited from `.Grid`
 * 2. Set the inter-cell spacing
 * 3. Reset white-space inherited from `.Grid`
 */

.Grid-cell {
  border: 2px solid #333;
  box-sizing: border-box;
  display: inline-block;
  font-size: 1rem; /* 1 */
  height: 100%;
  overflow: hidden;
  padding: 0 10px; /* 2 */
  position: relative;
  vertical-align: top;
  white-space: normal; /* 3 */
}

/* Cell states */

.Grid-cell.is-animating {
  background-color: #fffdec;
}

/* Cell dimension modifiers
   ========================================================================== */

.Grid-cell--1 { width: 10%; }
.Grid-cell--2 { width: 20%; }
.Grid-cell--3 { width: 30%; }
.Grid-cell--4 { width: 40%; }
.Grid-cell--5 { width: 50%; }

/* Cell modifiers
   ========================================================================== */

.Grid-cell--detail,
.Grid-cell--important {
  border-width: 4px;
}

JavaScript

  • Use soft tabs set to 2 spaces.
function() {
∙∙var name;
}
  • Place 1 space before the leading brace.
obj.set('foo', {
  foo: 'bar'
});

test('foo-bar', function() {
});
  • No spaces before semicolons.
var foo = {};
  • Keep parenthesis adjacent to the function name when declared or called.
function foo() {
}

foo();
  • Spaces are required around binary operators.
// assignments
var foo = bar + 'a';

// conditionals
if (foo === 'bara') {
}

// parameters
function(test, foo) {
}
  • Use YUIDoc comments for documenting functions.
  • Use // for single line comments.
/**
Description

More details on separate line. This commend block can wrap at 80 characters.

@method use tags from YUIDoc 
*/
function foo() {
  var bar = 5;

  // multiplies `bar` by 2. Single line comment.
  fooBar(bar);

  console.log(bar);
}

Objects

  • Use literal form for object creation.
var foo = {};
  • Pad single-line objects with white-space.
var bar = { color: 'orange' };

Arrays

  • Use literal form for array creation (unless you know the exact length).
var foo = [];
  • If you know the exact length and know that array is not going to grow, use Array.
var foo = new Array(16);
  • Use push to add an item to an array.
var foo = [];
foo.push('bar');

Strings

  • Use 'single quotes'.

Variables

  • Put all non-assigning declarations on one line.
var a, b;
  • Use a single var declaration for each assignment.
var a = 1;
var b = 2;
  • Declare variables at the top of their scope.
function foo() {
  var bar;

  console.log('foo bar!');

  bar = getBar();
}

Commas

  • Skip trailing commas in ES5.
  • Use trailing commas in ES6+.

ES5

var foo = [1, 2, 3];
var bar = { a: 'a' };

ES6

let foo = [1, 2, 3,];
let bar = { a: 'a', };
  • Skip leading commas.
var foo = [
  1,
  2,
  3
];

Semicolons

  • Use semicolons.

Block Statements

  • Use spaces.
// conditional
if (notFound) {
  return 0;
} else {
  return 1;
}

switch (condition) {
  case 'yes':
    // code
    break;
}

// loops
for (var key in keys) {
  // code
}

for (var i = 0; i < keys.length; i++) {
  // code
}

while (true) {
  // code
}

try {
  // code that throws an error
} catch (e) {
  // code that handles an error
}
  • Opening curly brace should be on the same line as the beginning of a statement or declaration.
function foo() {
  var obj = {
    val: 'test'
  };

  return {
    data: obj
  };
}

if (foo === 1) {
  foo();
}

for (var key in keys) {
  bar(e);
}

while (true) {
  foo();
}
  • Keep else and its accompanying braces on the same line.
if (foo === 1) {
  bar = 2;
} else {
  bar = '2';
}

if (foo === 1) {
  bar = 2;
} else if (foo === 2) {
  bar = 1;
} else {
  bar = 3;
}

Conditional Statements

  • Use === and !==.
  • Use curly braces.
if (notFound) {
  return;
}
  • Use explicit conditions.
if (arr.length > 0) {
  // code
}

if (foo !== '') {
  // code
}

Properties

  • Use dot-notation when accessing properties.
var foo = {
  bar: 'bar'
};

foo.bar;
  • Use [] when accessing properties with a variable.
var propertyName = 'bar';
var foo = {
  bar: 'bar'
};

foo[propertyName];

Functions

  • Make sure to name functions when you define them.
function fooBar() {
}

Arrow Functions

  • Make sure arrow functions are done on multiple lines.
var foo = [1,2,3,4].map((item) => {
  return item * 2;
});

Function Arguments

arguments object must not be passed or leaked anywhere. See the reference.

  • Use a for loop with arguments (instead of slice).
function fooBar() {
  var args = new Array(arguments.length);

  for (var i = 0; i < args.length; ++i) {
    args[i] = arguments[i];
  }

  return args;
}
  • Don't re-assign the arguments.
function fooBar() {
  arguments = 3;
}

function fooBar(opt) {
  opt = 3;
}
  • Use a new variable if you need to re-assign an argument.
function fooBar(opt) {
  var options = opt;

  options = 3;
}

Function Chains

Long-chained function calls and Promises should be indented. Add a line-break before each subsequent . function invocation in the chain. A final comment can (optionally) be added to visually close the block. The fingers crossed emoji works quite nicely.

RSVP.resolve()
  .then(function() {
    return run('sudo', ['apt-get', 'update']);
  })
  .then(function() {
    return setupChrome();
  })
  .then(function() {
    return run('./node_modules/.bin/testem', ['launchers']);
  })
  .then(function() {
    return run('./node_modules/.bin/testem', ['ci', '--file', 'testem.travis-browsers.js', '--port', '7000']);
  })
  .catch(function(error) {
    console.error(error.stack);
    process.exit(1);
  })
  .finally(function() {
    process.exit(0);
  });
// 🤞 "fingers crossed" emoji

Rest Parameters

Since Babel implements Rest parameters in a non-leaking matter you should use them whenever applicable.

function foo(...args) {
  args.forEach((item) => {
    console.log(item);
  });
}

Destructuring

When decomposing simple arrays or objects, prefer destructuring.

// array destructuring
var fullName = 'component:foo-bar';
var [
  first,
  last
] = fullName.split(':');
// object destructuring
var person = {
  firstName: 'Stefan',
  lastName: 'Penner'
};

var {
  firstName,
  lastName
} = person;

References

Based on work at: