This document intends to give an overview of our general approach to front end development and should serve as a guide to get you started on most projects. Please remember that this is a living document and will change over time as new ideas, methods, devices, browsers and W3C standards come about.
- HTML
- CSS
- JavaScript
All pages must use HTML in order to display anything in the browser, but while the raw markup is hidden from the end users, developers need to be able to quickly understand and interpret the HTML in order to work with it. With this in mind, readability is key above everything else.
HTML5 for websites and and XHTML 1.0 Transitional for emails.
<!-- Websites -->
<!DOCTYPE html>
<!-- Emails -->
<!DOCTYPE html>
This style guide should serve as some simple guidelines to help you craft HTML documents.
Indent with 2 spaces. Absolutely no tabs. Each nested element requires indentation.
<!-- Bad -->
<div>
<h1>Hello World!</h1>
</div>
<!-- Good -->
<div>
<h1>Hello World!</h1>
</div>
No line length limits, but be sensible.
Block elements (main
, section
, header
, div
, h1
, p
etc) on their own line
<!-- Bad -->
<div>
<ul><li>List item</li></ul>
</div>
<!-- Good -->
<div>
<ul>
<li>List item</li>
</ul>
</div>
Inline elements (a
, span
, etc) can sit on the same line or on their own depending on preference
<!-- Good -->
<p>Lorem ipsum <a href="#">click here</a></p>
<!-- Also Good -->
<p>
Lorem ipsum
<a href="#">click here</a>
</p>
Add a space between each attribute
<!-- Bad -->
<input type="text" name="first-name"value="Catch"class="control__input">
<!-- Good -->
<input type="text" name="first-name" value="Catch" class="control__input">
Note: always use "
for attributes NEVER '
Here at Catch we use SASS (SCCS syntax) and Autoprefixer to author CSS. SASS provides a solid set of features for writing clean and concise styles with minimal effort and repetition, while Autoprefixer ensures that our final CSS ships with the latest vendor prefixes.
In terms of CSS style, try and think as abstractly and reusable as possible to ensure that your CSS scales well, and remember to spend time to refine your code as it grows. Think in modules, blocks, elements, templates and atoms. If you're reusing lots of styles, consider promoting them to their own class.
While SASS allows us to loop and include and often comes with automagic features, it can lead down some bad routes for you and your website when it comes to maintenance. Always try and keep your SASS as close to the final CSS as possible.
A typical SASS folder structure looks like this:
styles.scss
/includes/
_includes.scss
_variables.scss
_mixins.scss
/base/
_base.scss
_reset.scss
_typography.scss
/components/
_components.scss
_header.scss
Includes should always contain non-compiling SASS such as functions, mixins and variables
Base SASS files should always contain basic theming styles which are required to build out basic page elements like paragraphs, lists, headings along with grid systems and anything that is useful ACROSS the website.
Components are exactly as they sound. They should contain styling for self contained blocks which fit into wider parts of the website. Components can be website headers, footers, forms, carousels etc. Components should be abstract and focus on appearance and functionality rather than content or context.
This style guide is heavily influenced by the Code Guide by @mdo. Please have a read through this as well if you have time.
Readability is key, SASS allows you to do great things, but can often lead to a spider's nest of code. Also remember to comment as much as you like. More comments mean happier developers! If your SASS is not syntax highlighted, have a look for a relevant package for your text editor to ensure it renders like standard CSS.
Indent with 2 spaces. Absolutely no tabs.
// Bad
h1 {
text-transform: uppercase;
}
// Good
h1 {
text-transform: uppercase;
}
Soft (desirable) limit of 80, hard limit of 120.
- Only one selector per line for multi-selector rules
- Add a space before opening curly brace
{
- Trailing curly braces on their own line
}
- Each declaration should be on it's own line even if there is just one declaration per selector
// Bad
input[type="number"] { border: solid grey 1px;}
// Bad
input[type="number"], input[type="tel"] {
border: solid grey 1px;
}
// Really bad
input[type="number"],input[type="tel"]{border: solid grey 1px;}
// Good
input[type="number"],
input[type="tel"] {
border: solid grey 1px;
}
Ensure there's a line between each CSS rule, even those that are nested.
// Bad
h1 {
font-size: 3rem;
}
h2 {
font-size: 3rem;
}
// Good
h1 {
font-size: 3rem;
}
h2 {
font-size: 3rem;
}
// Bad
p {
margin: 1em 0;
a {
color: inherit;
text-decoration: underline;
}
}
// Good
p {
margin: 1em 0;
a {
color: inherit;
text-decoration: underline;
}
}
// Immediately nested selectors are fine like this, but consider minimising nesting like this
// Good
p {
a {
color: inherit;
text-decoration: underline;
}
}
Try not to nest too much. Nesting leads to unnecessary specificity in your CSS which makes it rigid and hard to edit.
Consider that:
.field-carousel {
.carousel {
.carousel__item {
span {
a {
display: none;
}
}
}
}
}
Compiles to:
.field-carousel .carousel .carousel__item span a {
display: none;
}
Try not to reflect the HTML structure with nesting. This is a really bad route to take, is usually completely unnecessary and will cause problems when you need to amend styles in media queries etc.
// No one wants to be doing this. If you can simplify, then please do!
@media (min-width: 768px) {
.field-carousel .carousel .carousel__item span a {
display: inline-block;
}
}
Nesting in SASS is a really great way of writing BEM quickly and in a less error-prone manner:
.card {
border: solid grey 1px;
&--featured {
border-color: blue;
}
&__content {
padding: 20px;
&__title {
font-size: 2rem;
}
}
}
Compiles to:
.card {
border: solid grey 1px;
}
.card--featured {
border-color: blue;
}
.card__content {
padding: 20px;
}
.card__content__title {
font-size: 2rem;
}
No nested CSS, no specificity, just semantic and clean OOCSS styles which are easy to target in JS, media queries and external stylesheets if necessary.
Variables are great for storing units, integers and colour values. Try to namespace and group variables by their usage like so for more consistency and clarity when away from their use.
// Colours
$colour-red: #f00;
$colour-blue: #00f;
// Dimensions
$dimension-border-radius: 5px;
$dimension-padding: 20px;
For variable variations consider using modifiers on top of existing variable names
// Colour variations
$colour-red: #f00;
$colour-red-dark: darken(#f00, 20%);
// Dimension variations
$dimension-border-radius: 5px;
$dimension-border-radius-lg: ($dimension-border-radius * 2);
You can scope variables inside selectors to avoid having a master list of unrelated unit/colour values. When doing this, remember to ensure that you leave a space before the declarations for that rule-set.
.input {
$height: 30px;
height: $height;
margin-bottom: ($height / 2);
}
Moving forward we are using the Bootstrap grid system to offer a consistent and flexible way of laying out elements in templates. You can read more about this grid here.
The grid should be used to arrange and organise elements on the page. With this in mind, the brand specific styles you create should rely on an outer grid to contain it, rather than incorporating their own bespoke dimensions. This will ensure that your CSS is a flexible and reusable as possible.
Modules are self contained sets of styling that should be reusable across the website. Don't worry about being too granular, it's fine to have the styling of generic form elements in a single module file for instance.
Block Element Modifier (BEM) is a modular approach to writing CSS. You can read more about BEM here to get started. Using BEM is a simple way of organising and structuring your CSS and opens up your CSS for easy extension.
// Block (wraps elements)
.form-control {
...
}
// Elements (is a descendant of parent block)
.form-control__input {
...
}
// Modifier (modifies the block)
.form-control--valid {
...
}
// Modifier (modifies the block element)
.form-control__input--disabled {
...
}
Media queries should be used inversely using min-width
to ensure websites stay mobile first. To compliment the bootstrap grid, there are some preset variables to use in your media queries to ensure everyone's on the same page:
@media (min-width: $screen-xs-min) {
// At least 480px;
}
@media (min-width: $screen-sm-min) {
// At least 768px;
}
@media (min-width: $screen-md-min) {
// At least 992px;
}
@media (min-width: $screen-lg-min) {
// At least 1200px;
}
Media queries should sit at the bottom of a SASS file to target contextual rules like so:
// form-control.scss
.form-control {
...
}
...
@media (min-width: 768px) {
.form-control {
...
}
}
Don't do this:
// form-control.scss
.form-control {
...
}
...
@media (min-width: 768px) {
h1 {
...
}
}
All projects should utilise normalize.css
for basic element resets to ensure that styles start off consistently for all browsers.
We're utilising node-sass
for building. If you don't have node installed you can install in with HomeBrew using:
brew install node
You should also ensure that you have node-sass
, and autoprefixer installed globally with NPM:
npm install -g node-sass postcss-cli autoprefixer
The commands below assume that you have the package.json file in the root of the folder you're executing them from.
When you have everything installed, simply run from the location of the package.json file
npm install
This will install dependencies and run the build script for the first time.
To build again:
npm run build
To watch changes to SASS and compile as you work you can setup a watcher locally. If you don't have the node watch
package globally installed:
npm install -g watch
To watch styles
watch 'npm run build' ./path-of-dir-to-watch
Great tools, but kind of superfluous for our needs. Building using NPM and global packages is a quick and awesome winner. See here for a detailed overview
JavaScript (JS) is a beautiful and thoroughly enjoyable language when understood and done right. There is a lot of right/wrong guides when it comes to writing JS and indeed it has fallen victim to trends and fads, but behind each glossy framework and responsive carousel jQuery plugin is a pretty solid language, which - while often lacking in top level features - should provide a decent foundation for anything you can dream of. Paramount to the success of your JS is readable and consistent code which adheres a certain style.
Here at Catch, our JS needs are quite minimal. With this in mind, these guidelines are anything but comprehensive, but should help you in getting off on the right foot for most projects undertaken at Catch HQ.
Note: This guide assumes a basic understanding of JavaScript. If there's anything that you'd like clarifying, the chances are it's already been asked on Stack Overflow, but sometimes a good, old fashioned chat is better, so come and grab someone!
This style guide is heavily influenced by the Airbnb ES5 Style Guide. Please have a read through this as well if you have time.
Readability is key. Above everything else, always try and name things sensibly and allow some breathing space in your code. We'd rather have more spaces and sensible grouping of computations and assignments than none at all. Also remember to comment as much as you like. More comments mean happier developers!
Indent with 2 spaces. Absolutely no tabs
// Bad
var foo = 'bar';
// Good
var foo = 'bar';
Soft (desirable) limit of 80, hard limit of 120.
Introduce spacing to ensure that parts of code are sensibly grouped into their various roles
var val = 1;
var id;
function init(opts) {
// Definition
var startVal;
// Assignment
id = opts.name;
// Control
if (val < 2) {
startVal = 0;
}
// Method call
plugin.compute(startVal);
}
if
, for
etc. should always have a space either side of the parenthesis ()
. Always use curly braces, even for single lines. Functions should have no space before the parens.
// Bad
if(someVal){
someVal += 1;
}
// Really bad
if (someVal) someVal += 1;
// Good
if (someVal) {
someVal += 1;
}
// Good
for (i = 0; i < 10; i ++) {
someVal += i;
}
// Bad
function (i) {
...
}
// Bad
function(i){
...
}
// Bad
function(i)
{
...
}
// Good
function(i) {
...
}
Use spaces with operators
var c = a + b;
Instantly Invoked Function Expressions, or closures are used to isolate the scope of your javascript file. If you've ever used jQuery in noConflict()
mode the chances are you've come across one of these before.
Always ensure there is a trailing comma at the end of the IIFE to avoid invokception through code concatenation
(function() {
// JavaScript magic goes here
})();
Variables from outside the IIFE's scope can be passed in via reference like so:
(function($) {
// jQuery jazz goes here
})(jQuery);
Always enable strict mode at the top of your IFFE to ensure more predictable and consistent behaviour from browsers:
(function() {
'use strict';
// JavaScript magic goes here
})();
Always use the var
keyword to declare a variable. Not doing so will populate the global scope in strict mode which is bad times fo your and your dependencies.
var foo = 'bar';
Always declare each variable on a new line with a new var keyword
var foo = 'bar';
var bar = foo;
var baz = {};
Always use camel casing for no-specific variables
var myObject = {
value: 1
}
Prefix any jQuery object with a $
for readability
var $myDiv = $('div');
Constant like values (those that will never be changed once set) can be styled like so:
var PLUGIN_NAME = 'my-plugin';
Declare all variables at the top of their scope; you can always assign them later if you need to.
function objectLength(obj) {
var length = 0;
var prop;
for (prop in obj) {
length ++;
}
return length;
}
Since we write all our JS inside IFFEs, all variables should be declared at the top of the closure after declaring strict mode
(function($) {
'use strict';
var intervalId;
var interval = 1000;
var $element = $('[data-element-name]');
intervalId = setInterval(function() {
clearTimeout(timeoutId);
$element.toggleClass('glow');
}, interval);
})(jQuery);
More detailed information on hoisting can be found here
Sometimes it makes sense to group variables into objects for cleaner and more semantic JS
var module = {
$header: $('.module-header'),
$content: $('.module-content')
};
var plugin = {
$button: $('.plugin-button'),
$content: $('.plugin-content')
};
// Hide module and plugin content
module.$content.hide();
plugin.$content.hide();
Functions are first class objects, which gives them extreme flexibility. Our advice is to keep functions as pure and obvious as possible.
You should always endeavour to name your functions to make debugging as easy as possible and avoid too many stack traces plagued by (anonymous function)
:
startCarousel(function carouselInitCallback(e) {
console.trace(e);
});
// Output
Object {...}
carouselInitCallback @ script.js:21
setupCarousel @ carousel.js:456
startCarousel @ carousel.js:521
Although not always practical, try to make functions self contained to avoid any nasty surprises in your JavaScript. Pure functions always return a value and do not alter values from outside their scope
// Pure
function incrementCounter(counter, increment) {
counter.value += parseInt(increment, 10);
return counter.value;
}
myCounter.value = incrementCounter(myCounter, 10);
// NOT Pure. Functions augments value from outside its scope
function incrementCounter(increment) {
myCounter.value += parseInt(increment, 10);
return myCounter.value;
}
myCounter.value = incrementCounter(myCounter, 10);
Keeping JS as DRY (Don't Repeat Yourself) as possible is paramount to creating clean and concise code. It's always a good idea to group bits of code which will be invoked more than once and always need computation like so:
// Calculate position on load
calculateModulePosition($module);
// Calculate position on orientation change
$(window).on('orientationchange', function() {
calculateModulePosition($module);
});
function calculateModulePosition($module) {
var dimensions = {
top: $module.offset().top,
height: $module.innerHeight()
};
dimensions.bottom = dimensions.top + dimensions.height;
if (dimensions.bottom > ... And so on...
}
When writing functions that accept arguments it's often easier to accept a single object argument for changeable values:
// Initial code
function createModule(width, height, done) {
width = width || 300;
height = height || 200;
...
done.call({});
}
// Edited code
function createModule(width, height, offset, done) {
width = width || 300;
height = height || 200;
offset = offset || 0;
...
done.call({});
}
// Further edited code
function createModule(width, height, offset, resize, done) {
width = width || 300;
height = height || 200;
offset = offset || 0;
// Optional argument means we have to check arguments and add more code
if (typeof resize === 'function' && typeof done !== 'function') {
done = resize;
}
...
done.call({});
}
// A better approach using composition and extension
function createModule(opts, done) {
var defaults = {
width: 300,
height: 200,
offset: 0,
};
opts = $.extend({}, defaults, opts);
...
done.call({});
}
Objects are the building blocks of JavaScript. Functions are objects as are Arrays, which often gets confusing, especially when it comes to array like objects
You should always define objects using the literal notation
// Don't do this
var defaults = new Object();
// Do this
var defaults = {};
One thing that simple objects are incredibly useful for is key, value parings like associative arrays in PHP.
var defaults = {
width: 300
};
function getDefault(key) {
// Check we have a value before returning it
if (! (key in defaults)) {
return false;
}
// Access value by key
return defaults[key];
}
Just like objects, you should always define arrays using the literal notation
// Don't do this
var list = new Array();
// Do this
var list = [];
jQuery is very powerful and often overlooked for its simplicity in shimming the different JS quirks across different browsers. Below are some simple tricks to make your life easier with jQuery.
Using parent scopes is more performance friendly and avoids querying the entire DOM. It also ensures there a solid relationship between larger plugins/modules/blocks and their child elements.
var elements = {
$module: $('.module')
};
elements.$header = elements.$module.find('.module__header');
elements.$footer = elements.$module.find('.module__footer');
Here's a simple (and contrived), but flexible approach to creating jQuery plugins:
(function($) {
// Define jQuery plugin
$.fn.myPlugin = function myPluginDefinition(opts) {
return plugin($(this), opts);
};
function plugin($element, opts) {
opts = opts || {};
// Keep track of your plugin name
var PLUGIN_NAME = 'myPlugin';
// Assign all your public API methods to a single object
var api = {};
// Pair methods with the API
api.getName = getName;
// Returning the element data assignment will link our API to the element while allowing chaining with other jQuery methods
return $element.data(PLUGIN_NAME, api);
// All inner functions will be hoisted to the top of the scope,
// but placing them down here allows the business logic to be immediately readable in the plugin
function getName() {
return PLUGIN_NAME;
}
}
})(jQuery);
Use data attributes to link functionality to elements that exist in the DOM. Using classes can cause conflicts with CSS and should be avoided whilst using IDs is inflexible.
Another advantage of using data attributes is that they can hold data which can be used to initialise plugins which creates a seamless link from back to front:
<!-- HTML -->
<div data-plugin-init="some-data"></div>
// JS
$('[data-plugin-init]').each(function() {
$(this).myPlugin({
name: $(this).attr('data-plugin-init')
});
});