</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>
Enforce standards mode and more consistent rendering in every browser possible with this simple doctype at the beginning of every HTML page.
<!DOCTYPE html>
<html>
<head>
</head>
</html>
From the HTML5 spec:
Authors are encouraged to specify a lang attribute on the root html element, giving the document’s language. This aids speech synthesis tools to determine what pronunciations to use, translation tools to determine what rules to use, and so forth.
Read more about the lang
attribute in the spec.
Head to Sitepoint for a list of language codes.
<html lang="en-us">
<!-- ... -->
</html>
Internet Explorer supports the use of a document compatibility <meta>
tag to specify what version of IE the page should be rendered as. Unless circumstances require otherwise, it’s most useful to instruct IE to use the latest supported mode with edge mode.
For more information, read this awesome Stack Overflow article.
<meta http-equiv="X-UA-Compatible" content="IE=Edge">
Quickly and easily ensure proper rendering of your content by declaring an explicit character encoding. When doing so, you may avoid using character entities in your HTML, provided their encoding matches that of the document (generally UTF-8).
<head>
<meta charset="UTF-8">
</head>
Per HTML5 spec, typically there is no need to specify a type
when including CSS and JavaScript files as text/css
and text/javascript
are their respective defaults.
<!-- External CSS -->
<link rel="stylesheet" href="code-guide.css">
<!-- In-document CSS -->
<style>
/* ... */
</style>
<!-- JavaScript -->
<script src="code-guide.js"></script>
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.
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="...">
A boolean attribute is one that needs no declared value. XHTML required you to declare a value, but HTML5 has no such requirement.
For further reading, consult the WhatWG section on boolean attributes:
The presence of a boolean attribute on an element represents the true value, and the absence of the attribute represents the false value.
If you must include the attribute’s value, and you don’t need to, follow this WhatWG guideline:
If the attribute is present, its value must either be the empty string or […] the attribute’s canonical name, with no leading or trailing whitespace.
In short, don’t add a value.
<input type="text" disabled>
<input type="checkbox" value="1" checked>
<select>
<option value="1" selected>1</option>
</select>
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="...">
Writing markup in a JavaScript file makes the content harder to find, harder to edit, and less performant. Avoid it whenever possible.
:
for each declaration.box-shadow
).rgb()
, rgba()
, hsl()
, hsla()
, or rect()
values. This helps differentiate multiple color values (comma, no space) from multiple property values (comma with space).0.5
instead of .5
and -0.5px
instead of -.5px
).#fff
. Lowercase letters are much easier to discern when scanning a document as they tend to have more unique shapes.#fff
instead of #ffffff
.input[type="text"]
. They’re only optional in some cases, and it’s a good practice for consistency.margin: 0;
instead of margin: 0px;
.Questions on the terms used here? See the syntax section of the Cascading Style Sheets article on Wikipedia.
/* Bad CSS */
.selector, .selector-secondary, .selector[type=text] {
padding:15px;
margin:0px 0px 15px;
background-color:rgba(0, 0, 0, 0.5);
box-shadow:0px 1px 2px #CCC,inset 0 1px 0 #FFFFFF
}
/* Good CSS */
.selector,
.selector-secondary,
.selector[type="text"] {
padding: 15px;
margin-bottom: 15px;
background-color: rgba(0,0,0,.5);
box-shadow: 0 1px 2px #ccc, inset 0 1px 0 #fff;
}
[class^="..."]
) on commonly occuring components. Browser performance is known to be impacted by these.Additional reading:
/* Bad */
#tasks-wrapper .tasks > div input[type="text"] { ... }
.task-item .label { ... }
/* Good */
.task-item__edit-title { ... }
.task-item__label { ... }
Nesting is very helpful to define element states such as active and hover, pseudo elements like before and after, and minimal child selectors that don’t justify an additional class name.
// Bad
.tasks-items {
// styles
> div {
// styles
.selector {
label {
// styles
&:hover {
// styles
}
}
}
}
}
// Good
.task-items {
// styles
}
.task-item {
// styles
}
.task-item__label {
// styles
&:hover {
// styles
}
}
Variables in Sass are a powerful way to define values in one place that can be reused in multiple places in your project. They allow you to make changes from a central point without needing to use find and replace across multiple files and directories. [ref]
_user-settings.scss
.// Variable names should describe the purpose or function
$red: red; // Bad
$yellow: yellow; // Bad
$brand-color: red; // Good
$accent-color: yellow; // Good
// Use hyphens to separate multiple words
$badVariable: red; // Bad
$bad_variable: red; // Bad
$good-variable: red; // Good
First, some basics:
This should be your first plan of attack. Using an @extend limits customizability but produces a more efficient CSS file. Smaller is better.
If you need to overwrite declarations of an @extend or need to pass values to it then you’ll want to set up a mixin and use @include instead. Mixins are powerful and are generally used to accomplish complicated or labor-intensive tasks. Some examples:
/* Extend a class */
.error {
color: red;
&:hover {
color: darken(red, 5%);
}
}
.my-element {
@extend .error;
}
/* Create a mixin and include it somewhere else */
@mixin icon-font($font-size: 16px) {
font-family: "Icon Font";
font-weight: normal;
font-size: $font-size;
}
.icon {
@include icon-font();
}
.large-icon {
@include icon-font(20px);
}
Code is written and maintained by people. Ensure your code is descriptive, well commented, and approachable by others. Great code comments convey context or purpose. Do not simply reiterate a component or class name.
Be sure to write in complete sentences for larger comments and succinct phrases for general notes.
/* Bad */
/* Modal header */
.modal-header {
...
}
/* Good */
/* Wrapping element for .modal-title and .modal-close */
.modal-header {
...
}
Place media queries inside their respective selector. Don’t have entire blocks of code dedicated to a single media query.
Feel free to create a media query mixin to simplify modifying your queries.
// Bad
.element {
// styles
}
.element-header {
// styles
}
@media (min-width: 480px) {
.element {
// styles
}
.element-header {
// styles
}
}
// Good
.element {
// styles
@media (min-width: 480px) {
// styles
}
}
.element-header {
// styles
@media (min-width: 480px) {
// styles
}
}
// Example mixin
@mixin breakpoint($point) {
@if $point == desktop {
@media (min-width: 960px) { @content; }
}
@else if $point == tablet {
@media (min-width: 720px) { @content; }
}
@else if $point == mobile {
@media (min-width: 320px) { @content; }
}
}
.element {
// styles
@include breakpoint(mobile) {
// styles
}
}
Even in cases where you only have one rule per selector, avoid single-line formatting. In the same way that consistent trailing commas in Javascript makes for cleaner diffs, so does maintaining consistent formatting across all CSS rules, regardless of the number.
/* Multiple declarations, one per line */
.sprite {
display: inline-block;
width: 16px;
height: 15px;
background-image: url(../img/sprite.png);
}
/* Single declarations, one per line too! */
.icon {
background-position: 0 0;
}
Strive to limit use of shorthand declarations to instances where you must explicitly set all the available values. Common overused shorthand properties include:
padding
margin
font
background
border
border-radius
Often times we don’t need to set all the values a shorthand property represents. For example, HTML headings only set top and bottom margin, so when necessary, only override those two values. Excessive use of shorthand properties often leads to sloppier code with unnecessary overrides and unintended side effects.
The Mozilla Developer Network has a great article on shorthand properties for those unfamiliar with notation and behavior.
/* Bad */
.element {
margin: 0 0 10px;
background: red;
background: url("image.jpg");
border-radius: 3px 3px 0 0;
}
/* Good */
.element {
margin-bottom: 10px;
background-color: red;
background-image: url("image.jpg");
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.btn
and .btn-danger
)..btn
is useful for button, but .s
doesn’t mean anything..js-*
classes to denote behavior (as opposed to style), but keep these classes out of your CSS./* Bad */
.t { ... }
.red { ... }
.header { ... }
/* Good */
.tweet { ... }
.important { ... }
.tweet-header { ... }
Thanks to compilation and compression, we’re able to organize styles into many small files without impacting the end user. Rules should be organized into small sets corresponding to the elements they style. Styles of unrelated elements should never be in the same file.
Consider the example on the right.
This rule obviously hides something, but it’s not clear what it’s hiding, or why. This code is unmaintainable, because its purpose is not obvious. Strive to write selectors and styles whose purpose and relationship is intuitive and obvious.
// Bad
.segmented .focus-bar-button {
// styles
&.selected:before,
&:active:before,
&.selected + li:before,
&:active + li:before,
&:first-child:before {
display: none
}
}
Recommended only. During the course of development this might not be realistic. Try to keep the following recommendations in mind.
Related property declarations should be grouped together following the order:
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.
For a complete list of properties and their order, please see Recess.
.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;
}
@import
Compared to <link>
s, @import
is slower, adds extra page requests, and can cause other unforeseen problems. Avoid them and instead opt for an alternate approach:
<link>
elementsFor more information, read this article by Steve Souders.
<!-- Use link elements -->
<link rel="stylesheet" href="core.css">
<!-- Avoid @imports -->
<style>
@import url("more.css");
</style>
For improved readability, wrap all math operations in parentheses with a single space between values, variables, and operators.
// Bad example
.element {
margin: 10px 0 $variable*2 10px;
}
// Good example
.element {
margin: 10px 0 ($variable * 2) 10px;
}
Set your editor to the following settings to avoid common code inconsistencies and dirty diffs:
Consider documenting and applying these preferences to your project’s .editorconfig
file. For an example, see the one in Bootstrap. Learn more about EditorConfig.
\n
not \r\n
.// Bad
function test (){
const x=y+5
const dog = {
breed: 'Dachshund'
, legs: 'short'
};
if (x>6)
return false;
if(isJedi){
console.log (x)
}
else{
console.log (y)
}
return x
}
// Good
function test() {
const x = y + 5;
const dog = {
breed: 'Dachshund',
legs: 'short',
};
if (x > 6) return false;
if (isJedi) {
console.log(x);
} else {
console.log(y);
}
return x;
}
// Bad
function() { return false; }
// Good
function() {
return false;
}
const
for all of your references; avoid using var
.let
instead of var
.let
is block-scoped rather than function-scoped like var
.let
and const
are block-scoped.const
/let
declaration per variable.const
s and then group all your let
s.// Bad
a = 1;
var b = 2;
var c = 3,
d = 4;
if (true) {
c += 1;
}
// Good
const a = 1;
const b = 2;
let c = 3;
let d = 4;
if (true) {
c += 1;
}
Use ===
and !==
over ==
and !=
.
Conditional statements such as the if statement evaulate their expression using coercion with the ToBoolean
abstract method and always follow these simple rules:
true
false
false
+0
, -0
, or NaN
, otherwise true
''
, otherwise true
if ([0]) {
// true
// An array is an object, objects evaluate to true
}
// Bad
if (name !== '') {
// ...stuff...
}
// Good
if (name) {
// ...stuff...
}
// Bad
if (collection.length > 0) {
// ...stuff...
}
// Good
if (collection.length) {
// ...stuff...
}
String
for Strings.parseInt
for Numbers and always with a radix for type casting.Boolean
for Booleans.// STRINGS
this.reviewScore = 9;
// Bad
const totalScore = this.reviewScore + '';
// Good
const totalScore = String(this.reviewScore);
// NUMBERS
const inputValue = '4';
// Bad
const val = new Number(inputValue);
const val = +inputValue;
const val = inputValue >> 0;
const val = parseInt(inputValue);
// Good
const val = Number(inputValue);
const val = parseInt(inputValue, 10);
// BOOLEANS
const age = 0;
// Bad
const hasAge = new Boolean(age);
// Good
const hasAge = Boolean(age);
const hasAge = !!age;
_
when naming private properties or methods.this
. Use arrow functions or Function#bind
.// Bad
function q() {
const OBJEcttsssss = {};
const this_is_my_object = {};
const that = this;
return function() {
console.log(that);
};
}
// Good
function query() {
const thisIsMyObject = {};
function thisIsMyFunction() {}
return () => {
console.log(this);
};
}
// Bad
function good_dog(options) {
this.__furColour__ = options.fur;
this.furColour_ = options.fur;
}
// Good
class GoodDog {
constructor(options) {
this._furColour = options.fur;
}
}
export default GoodDog;
// Bad
import GoodDog from './good_dog';
// Good
import GoodDog from './GoodDog';
// Good
function petSomeDogs() {
// ...
}
export default petSomeDogs;
/** ... */
for multi-line comments. Include a description, specify types and values for all parameters and return values.//
for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment.Prefixing your comments with FIXME
or TODO
helps other developers quickly understand if you’re pointing out a problem that needs to be revisited, or if you’re suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable.
// FIXME:
to annotate problems.// TODO:
to annotate solutions to problems.// Bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make(tag) {
let dogs = 1;
// there are no cats
let cats = 0;
let element = document.createElement(tag); // create element
return element;
}
// Good
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make(tag) {
// FIXME: this isn't nearly enough dogs
let dogs = 1;
// TODO: add more cats
let cats = 0;
// create element
let element = document.createElement(tag);
return element;
}
// ARRAY DESTRUCTURING
const arr = [1, 2, 3, 4];
// Bad
const first = arr[0];
const second = arr[1];
// Good
const [first, second] = arr;
// OBJECT DESTRUCTURING
// Bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// Good
function getFullName(obj) {
const { firstName, lastName } = obj;
return `${firstName} ${lastName}`;
}
// Best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
}
// MULTIPLE RETURN VALUES
// Bad
function processInput(input) {
return [left, right, top, bottom];
}
// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);
// Good
function processInput(input) {
return { left, right, top, bottom };
}
// the caller selects only the data they need
const { left, right } = processInput(input);
Import statements should be grouped in the following order:
// Bad
const StyleGuide = require('./StyleGuide');
module.exports = StyleGuide.es6;
// Bad
import * as StyleGuide from './StyleGuide';
export default StyleGuide.es6;
// Good
import StyleGuide from './StyleGuide';
export default StyleGuide.es6;
// Best
import { es6 } from './StyleGuide';
export default es6;
// Bad
import utils from './utils';
import $ from 'jquery';
// Good
import $ from 'jquery';
import utils from './utils';
''
for strings.// Bad
const name = "Capt. Janeway";
// Good
const name = 'Capt. Janeway';
// Bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// Bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// Good
function sayHi(name) {
return `How are you, ${name}?`;
}
Array#push
instead of direct assignment to add items to an array....
to copy arrays.Array#from
.// Bad
const items = new Array();
// Good
const items = [];
// Bad
items[items.length] = 'abracadabra';
// Good
items.push('abracadabra');
// Bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// Good
const itemsCopy = [...items];
// Good
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
this
, which is usually what you want, and is a more concise syntax.// Bad
const foo = function () {
// ...
};
// Good
function foo() {
// ...
}
// Bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// OK
let test;
if (currentUser) {
test = () => {
console.log('Yup.');
};
}
// Bad
[1, 2, 3].map(function (x) {
return x * x;
});
// Good
[1, 2, 3].map((x) => {
return x * x;
});
// Good
[1, 2, 3].map(x => x * x);
arguments
. This will take precedence over the arguments
object that is given to every function scope.arguments
, opt to use rest syntax ...
instead.Object#assign
to set default object values.// Bad
function nope(name, options, arguments) {
// ...
}
// Good
function yup(name, options, args) {
// ...
}
// Bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// Good
function concatenateAll(...args) {
return args.join('');
}
// Bad
function handleThings(options) {
// No! We shouldn't mutate function arguments.
// Double bad: if opts is falsy it'll be set to an object which may
// be what you want but it can introduce subtle bugs.
this.options = options || {};
// ...
}
// Good
function handleThings(options = {}) {
this.options = Object.assign({}, {
valueA: 'AAA', // defaults
}, options);
}
// LITERAL SYNTAX / OBJECT METHOD SHORTHAND / RESERVED WORDS
// Bad
const superman = new Object(
default: { clark: 'kent' },
private: true,
class: 'alien',
laserEyes: function (target) {
return `${target} is real dead`;
},
);
// Good
const superman = {
defaults: { clark: 'kent' },
hidden: true,
type: 'alien',
laserEyes(target) {
return `${target} is real dead`;
},
};
// COMPUTED PROPERTY NAMES
function getKey(k) {
return `a key named ${k}`;
}
// Bad
const obj = {
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// Good
const obj = {
name: 'San Francisco',
[getKey('enabled')]: true,
};
// PROPERTY VALUE SHORTHAND
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// Bad
const obj = {
episodeOne: 1,
lukeSkywalker,
twoJedisWalkIntoACantina: 2,
anakinSkywalker,
};
// Good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJedisWalkIntoACantina: 2,
};
// ACCESSING PROPERTIES
const luke = {
jedi: true,
age: 28,
};
// Bad
const isJedi = luke['jedi'];
// Good
const isJedi = luke.jedi;
class
. Avoid manipulating prototype
directly.extends
for inheritance. It is a built-in way to inherit prototype functionality without breaking instanceof
.toString()
method, just make sure it works successfully and causes no side effects.getVal()
and setVal('hello')
.isVal()
or hasVal()
.get()
and set()
functions, but be consistent.// USING CLASS
// Bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
// Good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}
// USING EXTENDS
// Bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
return this._queue[0];
}
// Good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}
// TOSTRING
class Jedi {
contructor(options = {}) {
this.name = options.name || 'no name';
}
getName() {
return this.name;
}
toString() {
return `Jedi - ${this.getName()}`;
}
}
// ACCESSORS
// Bad
dragon.age();
dragon.age(25);
// Good
dragon.getAge();
dragon.setAge(25);
// Bad
if (!dragon.born()) {
return false;
}
// Good
if (!dragon.isBorn()) {
return false;
}
// OK
class Jedi {
constructor(options = {}) {
const lightsaber = options.lightsaber || 'blue';
this.set('lightsaber', lightsaber);
}
set(key, val) {
this[key] = val;
}
get(key) {
return this[key];
}
}
When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event.
// Bad
$(this).trigger('listingUpdated', listing.id);
// ...
$(this).on('listingUpdated', function(e, listingId) {
// do something with listingId
});
// Good
$(this).trigger('listingUpdated', { listingId : listing.id });
// ...
$(this).on('listingUpdated', function(e, data) {
// do something with data.listingId
});
$
.// Bad
const sidebar = $('.sidebar');
// Good
const $sidebar = $('.sidebar');
// Bad
function setSidebar() {
$('.sidebar').hide();
// ...
$('.sidebar').css({
'background-color': 'pink'
});
}
// Good
function setSidebar() {
const $sidebar = $('.sidebar');
$sidebar.hide();
// ...
$sidebar.css({
'background-color': 'pink'
});
}
Gearbox follows PSR-2 standards for PHP development. This is an overview of the higher-level rules for PSR-2, for the full set of standards please reference the PHP-FIG docs.
The PSR-2 Standard extends the PSR-1 Standard, documented here.
use
declarations.abstract
and final
should be declared before the visibility; static
should be declared after the visibility.snake_case
.PascalCase
.camelCase
.<?php
namespace Vendor\Package;
use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
class Foo extends Bar implements FooInterface
{
const SAMPLE_CONSTANT = 12345678;
public function sampleMethod($a, $b = null)
{
if ($a === $b) {
bar();
} elseif ($a > $b) {
$foo->bar($arg1);
} else {
BazClass::bar($arg2, $arg3);
}
}
final public static function bar()
{
// ...
}
}
namespace
declaration.use
declarations should go after the namespace
declaration.use
keyword per declaration.use
block.<?php
namespace Vendor\Package;
use FooClass;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;
// ...
When making a method or function call:
Argument lists can be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list should be on its own line, with only one argument per line.
<?php
// Good
bar();
$foo->bar($arg1);
Foo::bar($arg2, $arg3);
// Good
$foo->bar(
$longArgument,
$longerArgument,
$muchLongerArgument
);
The general style rules for control structures are as follows:
The body of each structure should be enclosed by braces. This standardizes how the structures look, and reduces the likelihood of introducing errors as new lines get added to the body.
<?php
// Great
if ($expr1) {
// ...
} elseif ($expr2) {
// ...
} else {
// ...
}
// Terrific
foreach ($iterable as $key => $value) {
// ...
}
// Superb
switch ($expr) {
case 0:
echo 'First case, with a break';
break;
case 1:
echo 'Second case, which falls through';
// No break
case 2:
case 3:
case 4:
echo 'Third case, return instead of break';
return;
default:
echo 'Default case';
break;
}
function
keyword, and a space before and after the use
keyword.Like methods and functions, argument lists can be spread across multiple lines, using the same line-break and indentation style.
<?php
$closureWithArgs = function ($arg1, $arg2) {
// ...
};
$closureWithArgsAndVars = function ($arg1, $arg2) use ($var1, $var2) {
// ...
};
$longArgs_longVars = function (
$longArgument,
$longerArgument,
$muchLongerArgument
) use (
$longVar1,
$longerVar2,
$muchLongerVar3
) {
// ...
};
Use single and double quotes when appropriate. If you’re not evaluating anything in the string, use single quotes. You should almost never have to escape quotes in a string, because you can just alternate your quoting style.
Text that goes into attributes in Wordpress should be run through esc_attr()
so that single or double quotes do not end the attribute value and invalidate the HTML and cause a security issue. See Data Validation in the Codex for further details.
<?php
echo '<a href="/static/link" title="Yeah yeah!">Link name</a>';
echo "<a href='$link' title='$linktitle'>$linkname</a>";
Perl compatible regular expressions (PCRE, preg_
functions) should be used in preference to their POSIX counterparts. Never use the /e
switch, use preg_replace_callback
instead.
It’s most convenient to use single-quoted strings for regular expressions since, contrary to double-quoted strings, they have only two metasequences: \'
and \\
.
A ternary operator must always have a space before and after the : and ? characters, unless the desired output matches the logic used to define the output.
Ternary operators should be wrapped in braces for maximum readability.
<?php
$foo = ($bar ?: null);
$foo = ($bar > 3 ? 'baz' : null);
Prefer string values to just true
and false
when calling functions.
Since PHP doesn’t support named arguments, the values of the flags are meaningless, and each time we come across a function call like the examples above, we have to search for the function definition. The code can be made more readable by using descriptive string values, instead of booleans.
<?php
// Bad
function eat($what, $slowly = true) {
// ...
}
eat('mushrooms');
eat('mushrooms', true); // what does true mean?
eat('dogfood', false); // what does false mean? The opposite of true?
// Good
function eat($what, $speed = 'slowly') {
// ...
}
eat('mushrooms');
eat('mushrooms', 'slowly');
eat('dogfood', 'quickly');
<? console.log('Bad'); ?>
<?php console.log('Good'); ?>
<?php
// Bad – clever but not immediately clear
isset($var) || $var = some_function();
// Good – longer but clearer
if (!isset( $var )) {
$var = some_function();
}
/resources/views/layouts
directory and reference it using the Blade @extends
directive.@php
directive available it is preferred to do one of the following:
<!-- Bad -->
@php
setup_postdata($post);
@endphp
<!-- Good -->
@setup_postdata
<!-- Bad -->
@php
$events = get_posts([
'post_type' => 'event',
]);
@endphp
@foreach ($events as $event)
...
@endforeach
<!-- Good -->
@foreach (Event::fetchAll() as $event)
...
@endforeach
There are many useful directives provided in the base theme (or there will be once it exists).
For projects with unique functionality it is recommended to create your own blade directives in /app/lib/directives.php
.
<!-- Does something -->
@directive1
<!-- Does something else -->
@directive2
<!-- Does a third thing -->
@directive3
We need to add controller stuff here once it’s standardized.
<?php
namespace App\Controllers;
use Sober\Controller\Controller;
use App\Controllers\Interfaces\PostTypeInterface;
class TeamMember extends Controller implements PostTypeInterface
{
use Partials\PostType;
protected $template = 'single-team-member';
protected $acf = true;
/**
* Return team member teaser field values.
*
* @param boolean $featured
*
* @return array
*/
public static function teaser($featured = false)
{
$data = [
'title' => get_field('title'),
'headshot' => get_field('headshot'),
'featured' => $featured,
];
return package($data);
}
/**
* Return team member flyout field values.
*
* @return array
*/
public static function flyout()
{
$data = [
'title' => get_field('title'),
'photo' => get_field('photo'),
'bio' => get_field('bio'),
];
return package($data);
}
}
This is where naming conventions will go once they’re figured out.
Shopify is the only platform that we develop for that uses Liquid. Fortunately, they’ve done a great job on documentation and almost all of the info that you need can be found there.
The Objects link above is almost certainly the most important resource available to you when writing code for Shopify. It contains every single object you might come across during development and all of the values attached to them.
{% if boolean %}
<div class="special-case">
<!-- ... -->
</div>
{% endif %}
<!-- Bad -->
<h2>{{ section.settings.subheading }}</h2>
<!-- Good -->
{%- unless section.settings.subheading == blank -%}
<h2>{{ section.settings.subheading }}</h2>
{%- endunless -%}
In Liquid, you can include a hyphen in your tag syntax {{-
, -}}
, {%-
, and -%}
to strip whitespace from the left or right side of a rendered tag.
<!-- Fine, I guess... -->
{% unless string=='foobar' %}
{{ string }}
{% endunless %}
<!-- Better -->
{%- unless string == 'foobar' -%}
{{ string }}
{%- endunless -%}
=
==
, !=
, <
, >
, etc.if
, unless
, and
, or
|
, minus:
, divided_by:
, strip
{{ }}
, {% %}
{%if boolean%} <!-- Bad -->
{% if boolean %} <!-- Good -->
<!-- Bad -->
{%unless string=='foobar' %}
{{string}}
{%endunless%}
<!-- Good -->
{% unless string == 'foobar' %}
{{ string }}
{% endunless %}
<!-- Bad -->
{% assign some-boolean = true %}
{% assign pleaseNo = true %}
{% capture sillyString %}This is a string.{% endcapture %}
<!-- Good -->
{% assign some_boolean = true %}
{% capture some_string %}This is a string.{% endcapture %}
<!-- Move verbose logic into a variable -->
{% assign slides_class = 'no-slides' %}
{% if settings.slide_1 or settings.slide_2 or settings.slide_3 or settings.slide_4 or settings.slide_5 %}
{% assign slides_class = 'has-slides' %}
{% endif %}
<body class="{{ slides_class }}">
json
filter<!-- console.log() an object with the `| json` filter -->
<script charset="utf-8">
console.log({{ someObject | json }});
</script>
<!-- create a theme settings javascript object -->
<script charset="utf-8">
window.ThemeName = {};
ThemeName.settings = {{ settings | json }};
</script>
At its most basic, source control gives us the ability to store, share, and transport code and code changes, but the real value comes from the ability to record and replay the stories behind each change during the lifetime of a project.
This is not an intro to source control or a git how-to: we assume that you have an intermediate understanding of how to use git. If you’re new to git, chapters 2, 3, and 6 (through the section on interactive rebasing) of Scott Chacon’s git book will tell you the how.
It doesn’t matter if you use git’s commands or a GUI (see GitHub for Mac and GitKraken), but you need a confident understanding of what’s happening regardless. Knowing the commands often helps.
To keep our many repositories easy to find we follow a simple naming convention:
# Platforms
Shopify shopify
WordPress wp
Tumblr tumblr
Ghost ghost
# Examples
grid-shopify
The Grid Shopify theme.
bindery-wp
The Bindery WordPress theme.
shopify-skeleton
Our shopify theme framework.
wp-calcium
Our WordPress build system.
grid-johndoe-shopify
A customization of the Grid Shopify theme for John Doe (a client).
Things to do right away:
git config --global user.name "Your Name"
git config --global user.email "name@gearboxbuilt.com"
git config --global branch.autosetuprebase always
git config --global push.default simple
# To avoid waking up in a cold sweat mumbling vi commands...
# Use Sublime Text 2 (when installed) as your default editor.
git config --global core.editor "subl -n -w"
Never commit directly to master. Instead, create small branches for each and every topic you work on (such as a feature or bug fix), and create a pull request into master when the branch is complete.
The exception to this rule is version commits and tags.
If your branch cannot be named as a specific function/feature/bug then you can create a long-running branch with a name like multiple_fixes
.
When using this method, it’s important that the commits are clear and minimal (see Commits).
# Bad
git checkout -b newBranch
git checkout -b v0.0.4-dev
# Good
git checkout -b home_slideshow
git checkout -b fix_missing_video
# OK
git checkout -b multiple_fixes
Please read Stephen Ball’s Steel City Ruby 2013 presentation, Deliberate Git. It covers this topic very well.
Once you’ve read it, you’ll understand why you should:
WIP
is fine.When writing commits:
# Bad commit message
git commit -m "Fix login bug"
# Good commit message
git commit -m "Redirect user to the requested page after login"
# Great commit message
git commit
# opens $EDITOR
"Redirect user to the requested page after login
Users were being redirected to the home page after login, which is less
useful than redirecting to the page they had originally requested before
being redirected to the login form."
If you’re pushing a new branch, use the --set-upstream
(-u
) flag to automatically set the remote branch as your local branch’s tracking branch.
git push -u origin your_branch
So your pull request has been peer-reviewed and approved – nice work! Now it’s time to merge your changes into master.
You want to use a fast-forward merge (git merge --ff-only
). Merging this way prevents “merge bubbles” where commit messages are created that say that a branch has been merged. This is called a recursive merge and it muddies up the commit history.
# fast-forward merge
git checkout master
git merge --ff-only your_branch
# Didn't let you do it? No problem, it probably means that your
# branch is out of date. In other words, master has been updated
# since you originally branched off of it.
git checkout your_branch
git rebase master
git push -f
git checkout master
git merge --ff-only your_branch
# Whoa, did you just force push and re-write history?
# Ya, I did. At Gearbox a branch is your own. You own it. It's your baby.
# If somebody was working on your branch, you'd know about it because they
# would have talked to you.
# Special note: never re-write master's history.
Versioned libraries, both internal and public, should follow these practices:
major_feature.minor_feature/major_bugfix.minor_bugfix
numbering schemev
# on master
git add <your files>
git commit -m "Version bump to v1.1.0"
git tag -a v1.1.0 -m "Version v1.1.0 release"
git push && git push --tags
# Bump helper
# Place this in your bash profile or .zshrc
bump() { git commit -m "Bump to $1" && git tag -a $1 -m "Bump version $1"; }
# ooo, that's snazzy
# use it like this:
git add <your files>
bump v1.1.0
git push && git push --tags
# Version numbering scheme
Minor bug fix: v1.0.0 -> v1.0.1
Major bug fix: v1.0.0 -> v1.1.0
Minor feature: v1.0.0 -> v1.1.0
Major feature: v1.0.0 -> v2.0.0