HTML5 provides us with lots of semantic elements aimed to describe precisely the content. Make sure you benefit from its rich vocabulary.
<!-- bad --><divid=main><divclass=article><divclass=header><h1>Blog post</h1><p>Published: <span>21st Feb, 2015</span></p></div><p>…</p></div></div><!-- good --><main><article><header><h1>Blog post</h1><p>Published: <timedatetime=2015-02-21>21st Feb, 2015</time></p></header><p>…</p></article></main>Make sure you understand the semantics of the elements you're using. It's worse to use a semantic element in a wrong way than staying neutral.
<!-- bad --><h1><figure><imgalt=Companysrc=logo.png></figure></h1><!-- good --><h1><imgalt=Companysrc=logo.png></h1>Keep your code terse. Forget about your old XHTML habits.
<!-- bad --><!doctype html><htmllang=en><head><metahttp-equiv=Content-Typecontent="text/html; charset=utf-8" /><title>Contact</title><linkrel=stylesheethref=style.csstype=text/css/></head><body><h1>Contact me</h1><label> Email address: <inputtype=emailplaceholder=[email protected]required=required/></label><scriptsrc=main.jstype=text/javascript></script></body></html><!-- good --><!doctype html><htmllang=en><metacharset=utf-8><title>Contact</title><linkrel=stylesheethref=style.css><h1>Contact me</h1><label> Email address: <inputtype=emailplaceholder=[email protected]required></label><scriptsrc=main.js></script></html>Accessibility shouldn't be an afterthought. You don't have to be a WCAG expert to improve your website, you can start immediately by fixing the little things that make a huge difference, such as:
- learning to use the
altattribute properly - making sure your links and buttons are marked as such (no
<div class=button>atrocities) - not relying exclusively on colors to communicate information
- explicitly labelling form controls
<!-- bad --><h1><imgalt=Logosrc=logo.png></h1><!-- good --><h1><imgalt=Companysrc=logo.png></h1>While defining the language is optional, it's recommended to always declare it on the root element.
The HTML standard requires that pages use the UTF-8 character encoding. It has to be declared, and although it can be declared in the Content-Type HTTP header, it is recommended to always declare it at the document level.
<!-- bad --><!doctype html><title>Hello, world.</title><!-- good --><!doctype html><htmllang=en><metacharset=utf-8><title>Hello, world.</title></html>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.
<!-- bad --><!doctype html><metacharset=utf-8><scriptsrc=analytics.js></script><title>Hello, world.</title><p>...</p><!-- good --><!doctype html><metacharset=utf-8><title>Hello, world.</title><p>...</p><scriptsrc=analytics.js></script>While the semicolon is technically a separator in CSS, always treat it as a terminator.
/* bad */div{color: red } /* good */div{color: red}The box model should ideally be the same for the entire document. A global *{box-sizing: border-box} is fine, but don't change the default box model on specific elements if you can avoid it.
/* bad */div{width:100%; padding:10px; box-sizing: border-box} /* good */div{padding:10px}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}There are many ways to position elements in CSS. Favor modern layout specifications such as Flexbox and Grid, and avoid removing elements from the normal document flow, for example with position: absolute.
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-child > p ~*/* good */ div:first-of-type .infoAvoid 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}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 styles makes selectors and debugging harder. Avoid it when possible.
/* bad */li{visibility: hidden} li:first-child{visibility: visible} /* good */li+li{visibility: hidden}Don't duplicate style declarations that can be inherited.
/* bad */divh1,divp{text-shadow:01px0#fff} /* good */div{text-shadow:01px0#fff}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:5px10px20px}Prefer English over math.
/* bad */:nth-child(2n + 1){transform:rotate(360deg)} /* good */:nth-child(odd){transform:rotate(1turn)}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}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)}Use unitless values when you can. Favor rem if you use relative units. Prefer seconds over milliseconds.
/* bad */div{margin:0px; font-size:.9em; line-height:22px; transition:500ms} /* good */div{margin:0; font-size:.9rem; line-height:1.5; transition:.5s}If you need transparency, use rgba. Otherwise, always use the hexadecimal format.
/* bad */div{color:hsl(103,54%,43%)} /* good */div{color:#5a3}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}Don't use them.
/* bad */div{// position: relative; transform:translateZ(0)} /* good */div{/* position: relative; */will-change: transform}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)constarr=[1,2,3,4];constlen=arr.length;vari=-1;varresult=[];while(++i<len){varn=arr[i];if(n%2>0)continue;result.push(n*n);}// goodconstarr=[1,2,3,4];constisEven=n=>n%2==0;constsquare=n=>n*n;constresult=arr.filter(isEven).map(square);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.
// badconstmerge=(target, ...sources)=>Object.assign(target, ...sources);merge({foo: "foo"},{bar: "bar"});// =>{foo: "foo", bar: "bar" }// goodconstmerge=(...sources)=>Object.assign({}, ...sources);merge({foo: "foo"},{bar: "bar"});// =>{foo: "foo", bar: "bar" }Rely on native methods as much as possible.
// badconsttoArray=obj=>[].slice.call(obj);// goodconsttoArray=(()=>Array.from ? Array.from : obj=>[].slice.call(obj))();Embrace implicit coercion when it makes sense. Avoid it otherwise. Don't cargo-cult.
// badif(x===undefined||x===null){ ... }// goodif(x==undefined){ ... }Don't use loops as they force you to use mutable objects. Rely on array.prototype methods.
// badconstsum=arr=>{varsum=0;vari=-1;for(;arr[++i];){sum+=arr[i];}returnsum;};sum([1,2,3]);// => 6// goodconstsum=arr=>arr.reduce((x,y)=>x+y);sum([1,2,3]);// => 6If you can't, or if using array.prototype methods is arguably abusive, use recursion.
// badconstcreateDivs=howMany=>{while(howMany--){document.body.insertAdjacentHTML("beforeend","<div></div>");}};createDivs(5);// badconstcreateDivs=howMany=>[...Array(howMany)].forEach(()=>document.body.insertAdjacentHTML("beforeend","<div></div>"));createDivs(5);// goodconstcreateDivs=howMany=>{if(!howMany)return;document.body.insertAdjacentHTML("beforeend","<div></div>");returncreateDivs(howMany-1);};createDivs(5);Here's a generic loop function making recursion easier to use.
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.
// badconstsortNumbers=()=>Array.prototype.slice.call(arguments).sort();// goodconstsortNumbers=(...numbers)=>numbers.sort();Forget about apply(). Use the spread operator instead.
constgreet=(first,last)=>`Hi ${first}${last}`;constperson=["John","Doe"];// badgreet.apply(null,person);// goodgreet(...person);Don't bind() when there's a more idiomatic approach.
// bad["foo","bar"].forEach(func.bind(this));// good["foo","bar"].forEach(func,this);// badconstperson={first: "John",last: "Doe",greet(){constfull=function(){return`${this.first}${this.last}`;}.bind(this);return`Hello ${full()}`;}}// goodconstperson={first: "John",last: "Doe",greet(){constfull=()=>`${this.first}${this.last}`;return`Hello ${full()}`;}}Avoid nesting functions when you don't have to.
// bad[1,2,3].map(num=>String(num));// good[1,2,3].map(String);Avoid multiple nested function calls. Use composition instead.
constplus1=a=>a+1;constmult2=a=>a*2;// badmult2(plus1(5));// => 12// goodconstpipeline=(...funcs)=>val=>funcs.reduce((a,b)=>b(a),val);constaddThenMult=pipeline(plus1,mult2);addThenMult(5);// => 12Cache feature tests, large data structures and any expensive operation.
// badconstcontains=(arr,value)=>Array.prototype.includes ? arr.includes(value) : arr.some(el=>el===value);contains(["foo","bar"],"baz");// => false// goodconstcontains=(()=>Array.prototype.includes ? (arr,value)=>arr.includes(value) : (arr,value)=>arr.some(el=>el===value))();contains(["foo","bar"],"baz");// => falseFavor const over let and let over var.
// badvarme=newMap();me.set("name","Ben").set("country","Belgium");// goodconstme=newMap();me.set("name","Ben").set("country","Belgium");Favor IIFE's and return statements over if, else if, else and switch statements.
// badvargrade;if(result<50)grade="bad";elseif(result<90)grade="good";elsegrade="excellent";// goodconstgrade=(()=>{if(result<50)return"bad";if(result<90)return"good";return"excellent";})();Avoid for...in when you can.
constshared={foo: "foo"};constobj=Object.create(shared,{bar: {value: "bar",enumerable: true}});// badfor(varpropinobj){if(obj.hasOwnProperty(prop))console.log(prop);}// goodObject.keys(obj).forEach(prop=>console.log(prop));While objects have legitimate use cases, maps are usually a better, more powerful choice. When in doubt, use a Map.
// badconstme={name: "Ben",age: 30};varmeSize=Object.keys(me).length;meSize;// => 2me.country="Belgium";meSize++;meSize;// => 3// goodconstme=newMap();me.set("name","Ben");me.set("age",30);me.size;// => 2me.set("country","Belgium");me.size;// => 3Currying is a powerful but foreign paradigm for many developers. Don't abuse it as its appropriate use cases are fairly unusual.
// badconstsum=a=>b=>a+b;sum(5)(3);// => 8// goodconstsum=(a,b)=>a+b;sum(5,3);// => 8Don't obfuscate the intent of your code by using seemingly smart tricks.
// badfoo||doSomething();// goodif(!foo)doSomething();// badvoidfunction(){/* IIFE */}();// good(function(){/* IIFE */}());// badconstn=~~3.14;// goodconstn=Math.floor(3.14);Don't be afraid of creating lots of small, highly composable and reusable functions.
// badarr[arr.length-1];// goodconstfirst=arr=>arr[0];constlast=arr=>first(arr.slice(-1));last(arr);// badconstproduct=(a,b)=>a*b;consttriple=n=>n*3;// goodconstproduct=(a,b)=>a*b;consttriple=product.bind(null,3);Minimize dependencies. Third-party is code you don't know. Don't load an entire library for just a couple of methods easily replicable:
// badvar_=require("underscore");_.compact(["foo",0]));_.unique(["foo","foo"]);_.union(["foo"],["bar"],["foo"]);// goodconstcompact=arr=>arr.filter(el=>el);constunique=arr=>[...newSet(arr)];constunion=(...arr)=>unique([].concat(...arr));compact(["foo",0]);unique(["foo","foo"]);union(["foo"],["bar"],["foo"]);