Blog post
Published:
…
====== Frontend Guidelines ====== ===== HTML ===== ==== Semantics ==== HTML5 provides us with lots of semantic elements aimed to describe precisely the content. Make sure you benefit from its rich vocabulary.
Published: 21st Feb, 2015
…
Published:
…


==== Language ====
While defining the language and character encoding is optional, it’s
recommended to always declare both at document level, even if they’re
specified in your HTTP headers. Favor UTF-8 over any other character
encoding.
Hello, world.
Hello, world.
==== Performance ====
Unless there’s a valid reason for loading your scripts before your
content, don’t block the rendering of your page. If your style sheet is
heavy, isolate the styles that are absolutely required initially and
defer the loading of the secondary declarations in a separate style
sheet. Two HTTP requests is significantly slower than one, but the
perception of speed is the most important factor.
...
...
===== CSS ===== ==== Semicolons ==== While the semicolon is technically a separator in CSS, always treat it as a terminator.
/* bad */
div {
color: red
}
/* good */
div {
color: red;
}
==== Box model ====
Don’t change the default box model if you can avoid it.
/* bad */
div {
width: 100%;
padding: 10px;
box-sizing: border-box;
}
/* good */
div {
padding: 10px;
}
==== Flow ====
Don’t change the default behavior of an element if you can avoid it.
Keep elements in the natural document flow as much as you can. For
example, removing the white-space below an image shouldn’t make you
change its default display:
/* bad */
img {
display: block;
}
/* good */
img {
vertical-align: middle;
}
Similarly, don’t take an element off the flow if you can avoid it.
/* bad */
div {
width: 100px;
position: absolute;
right: 0;
}
/* good */
div {
width: 100px;
margin-left: auto;
}
==== Positioning ====
There are many ways to position elements in CSS but try to restrict
yourself to the properties/values below. By order of preference:
display: block;
display: flex;
position: relative;
position: sticky;
position: absolute;
position: fixed;
==== Selectors ====
Minimize selectors tightly coupled to the DOM. Consider adding a class
to the elements you want to match when your selector exceeds 3
structural pseudo-classes, descendant or sibling combinators.
/* bad */
div:first-of-type :last-chid > p ~ *
/* good */
div:first-of-type .info
Avoid overloading your selectors when you don’t need to.
/* bad */
img[src$=svg], ul > li:first-child {
opacity: 0;
}
/* good */
[src$=svg], ul > :first-child {
opacity: 0;
}
==== Specificity ====
Don’t make values and selectors hard to override. Minimize the use of
''id''’s and avoid ''!important''.
/* bad */
.bar {
color: green !important;
}
.foo {
color: red;
}
/* good */
.foo.bar {
color: green;
}
.foo {
color: red;
}
==== Overriding ====
Overriding styles makes selectors and debugging harder. Avoid it when
possible.
/* bad */
li {
visibility: hidden;
}
li:first-child {
visibility: visible;
}
/* good */
li + li {
visibility: hidden;
}
==== Inheritance ====
Don’t duplicate style declarations that can be inherited.
/* bad */
div h1, div p {
text-shadow: 0 1px 0 #fff;
}
/* good */
div {
text-shadow: 0 1px 0 #fff;
}
==== Brevity ====
Keep your code terse. Use shorthand properties and avoid using multiple
properties when it’s not needed.
/* bad */
div {
transition: all 1s;
top: 50%;
margin-top: -10px;
padding-top: 5px;
padding-right: 10px;
padding-bottom: 20px;
padding-left: 10px;
}
/* good */
div {
transition: 1s;
top: calc(50% - 10px);
padding: 5px 10px 20px;
}
==== Language ====
Prefer English over math.
/* bad */
:nth-child(2n + 1) {
transform: rotate(360deg);
}
/* good */
:nth-child(odd) {
transform: rotate(1turn);
}
==== Vendor prefixes ====
Kill obsolete vendor prefixes aggressively. If you need to use them,
insert them before the standard property.
/* bad */
div {
transform: scale(2);
*webkit-transform: scale(2);
*moz-transform: scale(2);
*ms-transform: scale(2);
transition: 1s;
*webkit-transition: 1s;
*moz-transition: 1s;
*ms-transition: 1s;
}
/* good */
div {
*webkit-transform: scale(2);
transform: scale(2);
transition: 1s;
}
==== Animations ====
Favor transitions over animations. Avoid animating other properties than
''opacity'' and ''transform''.
/* bad */
div:hover {
animation: move 1s forwards;
}
@keyframes move {
100% {
margin-left: 100px;
}
}
/* good */
div:hover {
transition: 1s;
transform: translateX(100px);
}
==== Units ====
Use unitless values when you can. Favor pixels over relative units and
seconds over milliseconds.
/* bad */
div {
margin: 0px;
font-size: 0.9rem;
line-height: 22px;
transition: 500ms;
}
/* good */
div {
margin: 0;
font-size: 15px;
line-height: 1.5;
transition: .5s;
}
==== Colors ====
If you need transparency, use ''rgba''. Otherwise, always use the
hexadecimal format.
/* bad */
div {
color: hsl(103, 54%, 43%);
}
/* good */
div {
color: #5a3;
}
==== Drawing ====
Avoid HTTP requests when the resources are easily replicable with CSS.
/* bad */
div::before {
content: url(white-circle.svg);
}
/* good */
div::before {
content: "";
display: block;
width: 20px;
height: 20px;
border-radius: 50%;
background: #fff;
}
==== Hacks ====
Don’t use them.
/* bad */
div {
// position: relative;
transform: translateZ(0);
}
/* good */
div {
/* position: relative; */
will-change: transform;
}
===== JavaScript =====
==== Performance ====
Favor readability, correctness and expressiveness over performance.
JavaScript will basically never be your performance bottleneck. Optimize
things like image compression, network access and DOM reflows instead.
If you remember just one guideline from this document, choose this one.
// bad (albeit way faster)
const arr = [1, 2, 3, 4];
const len = arr.length;
var i = -1;
var result = [];
while (++i < len) {
var n = arr[i];
if (n % 2 > 0) continue;
result.push(n * n);
}
// good
const arr = [1, 2, 3, 4];
const isEven = n => n % 2 == 0;
const square = n => n * n;
const result = arr.filter(isEven).map(square);
==== Statelessness ====
Try to keep your functions pure. All functions should ideally produce no
side-effects, use no outside data and return new objects instead of
mutating existing ones.
// bad
const merge = (target, ...sources) => Object.assign(target, ...sources);
merge({ foo: "foo" }, { bar: "bar" }); // => { foo: "foo", bar: "bar" }
// good
const merge = (...sources) => Object.assign({}, ...sources);
merge({ foo: "foo" }, { bar: "bar" }); // => { foo: "foo", bar: "bar" }
==== Natives ====
Rely on native methods as much as possible.
// bad
const toArray = obj => [].slice.call(obj);
// good
const toArray = (() =>
Array.from ? Array.from : obj => [].slice.call(obj)
)();
==== Coercion ====
Embrace implicit coercion when it makes sense. Avoid it otherwise. Don’t
cargo-cult.
// bad
if (x === undefined || x === null) { ... }
// good
if (x == undefined) { ... }
==== Loops ====
Don’t use loops as they force you to use mutable objects. Rely on
''array.prototype'' methods.
// bad
const sum = arr => {
var sum = 0;
var i = -1;
for (;arr[++i];) {
sum += arr[i];
}
return sum;
};
sum([1, 2, 3]); // => 6
// good
const sum = arr =>
arr.reduce((x, y) => x + y);
sum([1, 2, 3]); // => 6
If you can’t, or if using ''array.prototype'' methods is arguably abusive,
use recursion.
// bad
const createDivs = howMany => {
while (howMany--) {
document.body.insertAdjacentHTML("beforeend", "");
}
};
createDivs(5);
// bad
const createDivs = howMany =>
[...Array(howMany)].forEach(() =>
document.body.insertAdjacentHTML("beforeend", "")
);
createDivs(5);
// good
const createDivs = howMany => {
if (!howMany) return;
document.body.insertAdjacentHTML("beforeend", "");
return createDiv(howMany - 1);
};
createDivs(5);
==== Arguments ====
Forget about the ''arguments'' object. The rest parameter is always a
better option because:
* it’s named, so it gives you a better idea of the arguments the function is expecting
* it’s a real array, which makes it easier to use.
// bad
const sortNumbers = () =>
Array.prototype.slice.call(arguments).sort();
// good
const sortNumbers = (...numbers) => numbers.sort();
==== Apply ====
Forget about ''apply()''. Use the spread operator instead.
const greet = (first, last) => ''Hi ${first} ${last}'';
const person = ["John", "Doe"];
// bad
greet.apply(null, person);
// good
greet(...person);
==== Bind ====
Don’t ''bind()'' when there’s a more idiomatic approach.
// bad
["foo", "bar"].forEach(func.bind(this));
// good
["foo", "bar"].forEach(func, this);
// bad
const person = {
first: "John",
last: "Doe",
greet() {
const full = function() {
return ''${this.first} ${this.last}'';
}.bind(this);
return ''Hello ${full()}'';
}
}
// good
const person = {
first: "John",
last: "Doe",
greet() {
const full = () => ''${this.first} ${this.last}'';
return ''Hello ${full()}'';
}
}
==== Higher-order functions ====
Avoid nesting functions when you don’t have to.
// bad
[1, 2, 3].map(num => String(num));
// good
[1, 2, 3].map(String);
==== Composition ====
Avoid multiple nested function calls. Use composition instead.
// bad
const plus1 = a => a + 1;
const mult2 = a => a * 2;
mult2(plus1(5)); // => 12
// good
const pipeline = (...funcs) =>
val => funcs.reduce((a, b) => b(a), val);
const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);
addThenMult(5); // => 12
==== Caching ====
Cache feature tests, large data structures and any expensive operation.
// bad
const contains = (arr, value) =>
Array.prototype.includes
? arr.includes(value)
: arr.some(el => el === value);
contains(["foo", "bar"], "baz"); // => false
// good
const contains = (() =>
Array.prototype.includes
? (arr, value) => arr.includes(value)
: (arr, value) => arr.some(el => el === value)
)();
contains(["foo", "bar"], "baz"); // => false
==== Variables ====
Favor ''const'' over ''let'' and ''let'' over ''var''.
// bad
var obj = {};
obj["foo" + "bar"] = "baz";
// good
const obj = {
["foo" + "bar"]: "baz"
};
==== Conditions ====
Favor IIFE’s and return statements over if, else if, else and switch
statements.
// bad
var grade;
if (result < 50)
grade = "bad";
else if (result < 90)
grade = "good";
else
grade = "excellent";
// good
const grade = (() => {
if (result < 50)
return "bad";
if (result < 90)
return "good";
return "excellent";
})();
==== Object iteration ====
Avoid ''for...in'' when you can.
const shared = { foo: "foo" };
const obj = Object.create(shared, {
bar: {
value: "bar",
enumerable: true
}
});
// bad
for (var prop in obj) {
if (obj.hasOwnProperty(prop))
console.log(prop);
}
// good
Object.keys(obj).forEach(prop => console.log(prop));
==== Objects as Maps ====
While objects have legitimate use cases, maps are usually a better, more
powerful choice. When in doubt, use a ''Map''.
// bad
const me = {
name: "Ben",
age: 30
};
var meSize = Object.keys(me).length;
meSize; // => 2
me.country = "Belgium";
meSize++;
meSize; // => 3
// good
const me = Map();
me.set("name", "Ben");
me.set("age", 30);
me.size; // => 2
me.set("country", "Belgium");
me.size; // => 3
==== Curry ====
Currying might have its place in other languages, but avoid it in
JavaScript. It makes your code harder to read by introducing a foreign
paradigm while the appropriate use cases are extremely unusual.
// bad
const sum = a => b => a + b;
sum(5)(3); // => 8
// good
const sum = (a, b) => a + b;
sum(5, 3); // => 8
==== Readability ====
Don’t obfuscate the intent of your code by using seemingly smart tricks.
// bad
foo || doSomething();
// good
if (!foo) doSomething();
// bad
void function() { /* IIFE */ }();
// good
(function() { /* IIFE */ }());
// bad
const n = ~~3.14;
// good
const n = Math.floor(3.14);
==== Code reuse ====
Don’t be afraid of creating lots of small, highly composable and
reusable functions.
// bad
arr[arr.length - 1];
// good
const first = arr => arr[0];
const last = arr => first(arr.slice(-1));
last(arr);
// bad
const product = (a, b) => a * b;
const triple = n => n * 3;
// good
const product = (a, b) => a * b;
const triple = product.bind(null, 3);
==== Dependencies ====
Minimize dependencies. Third-party is code you don’t know. Don’t load an
entire library for just a couple of methods easily replicable:
// bad
var _ = require("underscore");
_.compact(["foo", 0]));
_.unique(["foo", "foo"]);
_.union(["foo"], ["bar"], ["foo"]);
// good
const compact = arr => arr.filter(el => el);
const unique = arr => [...Set(arr)];
const union = (...arr) => unique([].concat(...arr));
compact(["foo", 0]);
unique(["foo", "foo"]);
union(["foo"], ["bar"], ["foo"]);