Last Updated: July 20, 2017 12:08 AM
This document contains a very detailed outline of my most important JavaScript and CSS code conventions in a Style Guide format.
When each member of the team is collaborating on a project, we are all likely developing separate pieces of code. Ideally, each of those separate pieces of code should uniformly look and behave in such a way that they were produced by a single developer. There are many reasons behind this belief system:
- Easier to read and quickly inspect
- Uniform patterns allow others to quickly understand basic flow, meaning, and functionality
- The long-term value of software to an organization is in direct proportion to the quality of the codebase.
- Neatness counts
- General Ember Conventions
- Local Variables and Prototype Extensions
- Property Brace Expansion
- Super
- Override Init
- Observers
- Organizing Modules
- Run Loop
- Controllers
- Templates
- Routing
- JS Style
- JS Naming
- Variable Declarations
- Function Declarations
- Primitive Literals
- Operator Spacing
- Object Literals
- Comment Annotations
- JS Rules of Thumb
- Follow all project naming, structure and layout conventions as outlined in the Ember CLI documentation.
- Always leverage Ember CLI's eslint-plugin-ember for proper linter support. If you are using the Atom text editor you should use the AtomLinter/linter-eslint plugin to automatically apply
eslint --fix
on file save. - For a safer accessor, use
Ember.set(foo, 'bar', 'baz')
andEmber.get(foo, 'bar')
instead offoo.set('bar', 'baz')
orfoo.get('bar')
. See the following response by Ember core team member Stef Penner for more information. - Always use Ember's Javascript modules API syntax for importing values from modules. This allows Ember to leverage static analysis to eliminate unneeded code and ship less bytes down to the client. This also helps speed up Javascript parsing and evaluating time on the client.
- Prefer to use the the
Ember
implementation over raw javascript when possible. Example:
// GOOD - Using safe `Ember.get()` and `Ember.set()` accessors
// GOOD - Using Ember's Javascript modules API
// GOOD - Using `Ember.getWithDefault` and `Ember.isNone` instead of raw javascript
import Component from "@ember/component";
import { get, computed, getWithDefault } from "@ember/object";
import { isNone } from "@ember/utils";
export default Component.extend({
foo: 'bar',
fiz: computed('foo', {
get() {
// If `get(this, 'foo') === undefined` then return '', else return `get(this, 'foo')`
return getWithDefault(this, 'foo', '');
}
}),
qux: computed('foo', {
// Returns `true` if the passed value is `null` or `undefined`
get() {
return isNone(get(this, 'foo'));
}
})
});
//////////////////////////////////////////////////////////////////
// BAD - Using `<instance>.get` and `<instance>.set`
// BAD - Using globally destructured `Ember`
// BAD - Not using `Ember` specific APIs where you are able
const {
Component,
computed
} = Ember;
export default Component.extend({
foo: 'bar',
fiz: computed('foo', function() {
return this.get('foo') || '';
}),
qux: computed('foo', function() {
const foo = this.get('foo');
return foo === null || foo === undefined;
})
});
- Don't declare arrays or objects directly on Components. Instead, add them on init. This is so we can ensure that each instance of the Component has its own independent state.
- Don't introduce side-effects in computed properties. It will make reasoning about the origin of the change much harder and invalidates proper data flow throughout an Ember application. Data should flows down from the route and send actions back up to modify that data -- where the child does not actually own the data.
import Component from "@ember/component"
import { filterBy, alias } from "@ember/object/computed";
export default Component.extend({
// GOOD: Don't declare arrays or objects directly on Components
// GOOD: Override init
// GOOD: Don't forget `_super()`
init() {
this._super(...arguments);
set(this, 'users', [
{ name: 'Foo', age: 15 },
{ name: 'Bar', age: 16 },
{ name: 'Baz', age: 15 }
]);
},
// GOOD:
fifteen: filterBy('users', 'age', 15),
fifteenAmount: alias('fifteen.length'),
//////////////////////////////////////////////////////////////////
// BAD
fifteenAmount: 0,
fifteen: computed('users', function() {
const fifteen = this.get('users').filterBy('items', 'age', 15);
this.set('fifteenAmount', fifteen.length); // SIDE EFFECT!
return fifteen;
})
});
- Don't use
sendAction()
. Use closure actions instead of bubbling actions viasendAction()
. Ember's primary action handling method changed after release v1.13 with the introduction of closure actions. This change allows for a simpler paradigm where the action caller (child component for example) simply executes the passed-in "action" function with the proper context. This allows us to leverage some powerful techniques such as currying and passing back down return values after asynchronous action handlers finish executing on the route or controller.
Avoid using Ember's prototype extension syntax and instead prefer using corresponding functions from the Ember object. Create local variables from the Ember namespace.
// GOOD - Avoids using prototype extensions
import Component from "@ember/component";
import { computed } from "@ember/object";
import { alias } from "@ember/object/computed";
import { on } from "@ember/object/evented";
export default Component.extend({
first: alias('firstName'),
last: alias('lastName'),
fullName: computed('first', 'last', {
get() {
/* Code */
}
}),
sayHello: on('didInsertElement', {
get() {
/* Code */
}
})
});
//////////////////////////////////////////////////////////////////
// BAD - Using prototype extensions
// BAD - Using global `Ember` import
import Ember from 'ember';
export default Ember.Component.extend({
first: Ember.computed.alias('firstName'),
last: Ember.computed.alias('lastName'),
fullName: function() {
/* Code */
}).property('first', 'last'),
sayHello: function() {
/* Code */
}).on('didInsertElement')
});
Always prefer to use Ember's property brace expansion for computed property dependent keys. This will help improve readability and it provides less redundancy overall. The dependent keys must be together (without space) for the brace expansion to properly work.
// GOOD - Using property brace expansion
fullName: computed('user.{firstName,lastName}', {
// Code
})
//////////////////////////////////////////////////////////////////
// BAD - Not using property brace expansion
fullName: computed('user.firstName', 'user.lastName', {
// Code
})
Don't forget _super()
. When overriding framework methods, always call this._super(...arguments);
. This is necessary because certain methods need to setup certain things, and overriding them without a super
call will prevent that, leading to unexpected behavior.
Override init. Rather than using the object's init hook via Ember.on()
, override init and call _super
with ...arguments
. This allows you to control execution order.
// GOOD - Overriding `init()`
init() {
this._super(...arguments);
this.foo();
this.bar();
this.baz();
});
//////////////////////////////////////////////////////////////////
// BAD
foo: on('init', () => {
// ...
}),
bar: on('init', () => {
// ...
}),
baz: on('init', () => {
// ...
})
Never use observers. Usage of observers is very easy BUT it leads to hard to reason about consequences. Since observers eagerly compute we have these possible times period when data within the application is not actually "correct" or what you would expect. When we introduce many observers into an application this problem compounds on itself. Unless observers are necessary, it's better to avoid them. See the following video for more information: Observer Tip Jar by Stef Penner
// GOOD
// `{{input value=text key-up="change"}}`
import Controller from "@ember/controller";
import { get } from "@ember/object"
export default Controller.extend({
actions: {
change() {
console.log(`change detected: ${get(this, 'text')}`);
},
},
});
//////////////////////////////////////////////////////////////////
// BAD
// `{{input value=text}}`
export default Ember.Model.extend({
change: Ember.observer('text', function() {
console.log(`change detected: ${get(this, 'text')}`);
})
});
Leverage Ember CLI's eslint-plugin-ember addon for a great set of configurable rules for proper organization within components, controllers, models, and routes
Components:
- Services
- Default values
- Single line computed properties
- Multiline computed properties
- Observers
- Lifecycle Hooks
- Actions
- Custom / private methods
Controllers:
- Services
- Query params
- Default controller's properties
- Custom properties
- Single line computed properties
- Multi line computed properties
- Observers
- Actions
- Custom / private methods
Models:
- Attributes
- Relations
- Single line computed properties
- Multiline computed properties
- Other structures (custom methods etc.)
Routes:
- Services
- Default route's properties
- Custom properties
- model() hook
- Other route's methods (beforeModel etc.)
- Actions
- Custom / private methods
// GOOD - Component module organization follows best practices
// 1. Services
// 2. Default values
// 3. Single line computed properties
// 4. Multiline computed properties
// 5. Observers
// 6. Lifecycle Hooks
// 7. Actions
// 8. Custom / private methods
import Component from "@ember/component";
import { computed } from "@ember/object";
import { alias } from "@ember/object/computed";
import { inject } from "@ember/service"
export default Component.extend({
// 1. Services
i18n: inject(),
// 2. Defaults
role: 'sloth',
// 3. Single line Computed Property
vehicle: alias('car'),
// 4. Multiline Computed Property
levelOfHappiness: computed('attitude', 'health', {
get() {
let result = get(this, 'attitude') * get(this, 'health') * Math.random();
return result;
}
}),
// 6. Lifecycle Hooks
init() {
this._super(...arguments);
this._secretMethod();
},
// 7. All actions
actions: {
sneakyAction() {
return this._secretMethod();
}
},
// 8. Custom / private methods
_secretMethod() {
// custom secret method logic
}
});
Never use jQuery
without the Ember Run Loop. Using plain jQuery
invokes actions out of the Ember Run Loop. In order to have a control on all operations in Ember it's good practice to trigger actions in run loop.
// GOOD
import $ from "jquery";
import { bind } from "@ember/runloop";
$('#something-rendered-by-jquery-plugin').on(
'click',
bind(this, this._handlerActionFromController)
);
//////////////////////////////////////////////////////////////////
// BAD
Ember.$('#something-rendered-by-jquery-plugin').on('click', () => {
this._handlerActionFromController();
});
- Don't not use controllers
- Follow the recommended organization within controllers.
- Never use
ObjectController
orArrayController
; simply useController
. - Alias your model. It provides a cleaner code, it is more maintainable, and will align well with future routable components within Ember.
// GOOD - Alias your model
import Controller from "@ember/controller";
export default Controller.extend({
user: alias('model')
});
- Never use partials. Always use components instead. Partials share scope with the parent view and components will provide a consistent scope.
- Use new block syntax
- Use components in
{{#each}}
blocks. Contents of your each blocks should ideally be a single line. This will allow you to test the contents in isolation via unit tests, as your loop will likely contain more complex logic in this case.
- Prefer double quotes to single quotes in templates only
- Multi-line expressions should specify attributes starting on the second line, and should be indented one deeper than the start of the component or helper name.
- Dynamic segments should be underscored. This will allow Ember to resolve promises without extra serialization work.
// GOOD
this.route('foo', { path: ':foo_id' });
//////////////////////////////////////////////////////////////////
// BAD
this.route('foo', { path: ':fooId' });
Use the Gitflow Workflow model.
# Start the feature
git checkout -b feature/my-new-feature
# Sync a copy of the branch on the remote server
git push -u origin feature/my-new-feature
# Make individual atomic commits then push
git commit
git commit
git commit
# ...
git push
# For completing a feature get the most recent version of all the code.
git pull
# Merge the develop branch into the feature branch to make sure that nothing is outdated
git merge develop
# Finish the feature and Create PR
git push
# After the feature is merged, delete and prune the remote branch
git push origin --delete feature/my-new-feature
git remote prune origin
- Every commit should be constrained to a single piece of functionality and its dependencies. (Atomicity)
- Find good points in the development process to pause and commit pieces of your effort – a good rule of thumb is that any time you feel pleased that you're making progress you should commit your changes.
- Don't make changes to unrelated functionality inside of a single commit.
- If you encounter a bug when developing a new feature or another issue that needs to be addressed you can follow the example below.
Write good commit messages. Every commit message should explain in detail what it is the commit is trying to accomplish. The people reviewing the commit should be able to identify whether or not they should be the person reviewing it based solely upon the commit message.
This means that you should include:
- What types of functionality you worked on (JS, CSS, HBS). This doesn't have to be explicit, "update the login route event handler" would work to identify that you worked on JS.
- Explanations of why you used that particular approach to solve the problem, if necessary.
- A brief description of next steps if there is additional work that needs to be done.
- Do not remove default populated contents from commit messages. For example, merge commits will by default say what branch it was and what conflicts occurred during the merge.
# Store your current working state in your feature branch
git stash save "currently working on foo"
# Move to the develop branch or a feature branch to address the issue
git checkout develop
# Fix Code and Commit the change
git add my-fix.js
git commit
# Write a good commit message.
# Push the new version to the origin server.
git push
# Get back to what it is you were working on.
git checkout feature/feature-name
# If you need the fix in your current branch in order to move forward.
git merge develop
# Add back your work in progress.
git stash pop
Choose your favorite text editor. Some great options include:
- Atom (my personal favorite)
- Sublime Text
- VIM
For Atom users, I'm including my base config.cson
file located at ~/.atom/config.cson
:
"*":
core:
telemetryConsent: "no"
themes: [
"atom-dark-ui"
"monokai"
]
whitespace:
removeTrailingWhitespace: false
editor:
fontFamily: "Menlo"
fontSize: 14
invisibles: {}
nonWordCharacters: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?…"
preferredLineLength: 120
showIndentGuide: true
softWrap: true
softWrapAtPreferredLineLength: true
"exception-reporting":
userId: "884440fb-9e84-fcf9-8239-ecee423c921e"
"linter-eslint":
fixOnSave: true
"linter-ui-default":
panelHeight: 81
welcome:
showOnStartup: false
For Sublime Text 2 users, I'm including a nice starting point for your user-specific Preferences.sublime-settings
file:
{
"auto_complete_commit_on_tab": true,
"bold_folder_labels": true,
"color_scheme": "Packages/Color Scheme - Default/Monokai.tmTheme",
"default_encoding": "UTF-8",
"default_line_ending": "unix",
"detect_indentation": false,
"draw_minimap_border": true,
"draw_white_space": "all",
"ensure_newline_at_eof_on_save": true,
"file_exclude_patterns":
[
".DS_Store",
"Desktop.ini",
"*.pyc",
"._*",
"Thumbs.db",
".Spotlight-V100",
".Trashes"
],
"folder_exclude_patterns":
[
".git"
],
"font_size": 13,
"highlight_line": true,
"highlight_modified_tabs": true,
"hot_exit": false,
"ignored_packages":
[
"Vintage"
],
"match_brackets": true,
"match_brackets_angle": true,
"open_files_in_new_window": false,
"remember_open_files": true,
"rulers":
[
120
],
"save_on_focus_lost": true,
"shift_tab_unindent": true,
"show_encoding": true,
"show_line_endings": true,
"tab_size": 2,
"translate_tabs_to_spaces": true,
"trim_trailing_white_space_on_save": true,
"word_separators": "./\\()\"':,.;<>~!@#$%^&*|+=[]{}`~?",
"word_wrap": true,
"wrap_width": 120
}
For VIM users, the following is a great learning resource:
- Use soft-tabs with a 2 space indent.
- Be generous with well-written and clear comments.
- Each line should be no longer than 120 characters.
- Using Sublime Text 2, you can enforce this rule visually with a vertical ruler at 120 characters by customizing your User Settings file. (Shown above greater detail below in the
Sublime Text 2
section)
- Using Sublime Text 2, you can enforce this rule visually with a vertical ruler at 120 characters by customizing your User Settings file. (Shown above greater detail below in the
- For variables and functions, names should be limited to alphanumeric characters and, in some cases, the underscore character.
- Do NOT use: the dollar sign ("$") in any names.
- Variable names should be formatted in camel case.
- The first word of a boolean variable name will be "is".
- The first word of a variable name should be a noun (not a verb).
// GOOD
const accountNumber = "8401-1";
// BAD - Not camel case
const AccountNumber = "8401-1";
const account_number = "8401-1";
// BAD - Begins with a verb
const getAccountNumber = "8401-1";
- Function names should also be formatted using camel case.
- The first word of a function should be a verb (not a noun).
// GOOD
function doSomething() {
// code
}
// BAD - Not camel case
function Do_Something() {
// code
}
// BAD - Begins with a noun
function car() {
// code
}
- Never use
var
. Preferlet
to declare a block scope local variable. Useconst
to declare a constant whose value can not be re-assigned in the given scope (global or local).
// GOOD
const car = new Mustang();
// BAD - Global variable
car = new Mustang();
- All variables should be declared before used.
- Always use one
const
declaration per variable.
// GOOD
const store;
const count = 10;
const name = "Alex";
const found = false;
const empty;
// BAD - One `const` for multiple variables (much harder to spot global variable errors)
const store,
count = 10,
name = "Alex",
found = false;
empty;
// BAD - Improper initialization alignment, Incorrect indentation
const count =10;
const name = "Alex";
const found = false;
const empty;
// BAD - Multiple declarations on one line
const count = 10;
const name = "Alex";
const found = false, empty;
- Keep variable declarations as the first statements of a function's body.
- Group
const
declarations, then grouplet
declarations - Never initialize a variable to
null
if it does not have an initial value. In this case you will ONLY declare the variable.
// GOOD
const hogwarts = "Hogwarts School of Witchcraft and Wizardry";
const Gryffindor = "Gryffindor";
const Slytherin = "Slytherin";
let quidditchMatchTime = new Date();
let i;
let length;
// BAD - Improper Alignment. Multiple variables declared per line.
const hogwarts = "Hogwarts School of Witchcraft and Wizardry", Gryffindor = "Gryffindor", Slytherin = "Slytherin";
let quidditchMatchTime = new Date(),
i;
let length;
// BAD - Not using `const` when appropriate. Initialized variables to `null`.
let hogwarts = "Hogwarts School of Witchcraft and Wizardry";
let Gryffindor = "Gryffindor";
let Slytherin = "Slytherin";
let quidditchMatchTime = new Date();
let i = null;
let length = null;
- Do not use global variables. Implied global variables should also never be used.
// BAD - Improper initialization alignment, Incorrect indentation
const count =10;
const name = "Alex";
const found = false;
const empty;
// BAD - Multiple variable statements, Multiple declarations on one line
const count = 10;
const name = "Alex";
const found = false, empty;
- All functions should be declared before they are used to avoid hoisting confusion.
- There should be no space between the name of a function and the left parenthesis of its parameter list.
- For anonymous functions, there should be no space between the
function
keyword and the parentheses.
- For anonymous functions, there should be no space between the
- There should be one space between the right parenthesis and the left curly brace that begins the statement body.
- The body itself is indented two spaces and the closing right curly brace is aligned with the line containing the beginning of the declaration of the function.
- When a function is to be invoked immediately, the entire invocation expression should be wrapped in parens so that it is clear that the value being produced is the result of the function and not the function itself.
// GOOD
function outer(c, d) {
const e = c * d;
function inner(a, b) {
return (e * a) + b;
}
return inner(0, 1);
}
// GOOD
div.onclick = function() {
return false;
};
// BAD - Improper spacing of first line, left brace on wrong line
function doSomething (arg1, arg2)
{
return arg1 + arg2;
}
- Immediately invoked function expression format
// GOOD
(() => {
console.log('Herp Derp');
})();
- Never user
arguments
, instead use the rest syntax...
// GOOD
function concatAll(...args) {
return args.join('');
}
// BAD - `arguments` is not explicit and not an actual JS `Array`
function concatAll() {
const args = [].slice.call(arguments);
return args.join('');
}
- Use the default parameter syntax rather than manipulating function arguments within the function body.
// GOOD
function foo(opts = {}) {
//...
}
// BAD - Mutating function arguments in the body
function foo(opts) {
opts = opts || {};
//...
}
- Use arrow functions notation in place of a normally used anonymous function. Arrow functions will execute with the correct context of
this
. - If the function body is small and will cleanly fit on one-line, the you may omit the braces and use the implicit return value.
- Always use parentheses around the arguments (including single arguments) for readability
// GOOD
[1,2,3].map((x) => {
return x * x;
});
// GOOD (Even Better)
[1,2,3].map((x) => x * x);
// BAD
[1,2,3].map(function(number) {
return number * number;
});
- Strings should appear on a single line. Don't use a slash to create a new line in a string.
- If you need multi-line strings use the template literal format.
// GOOD
const name = "Alex DiLiberto";
// GOOD
const greeting = `Hello, my name
is ${name}`;
// BAD
const greeting = "Hello, my name \
is Alex DiLiberto.";
Numbers should be written as decimal integers, e-notation integers, hexadecimal integers, or floating-point decimals with at least one digit before and one digit after the decimal point. Do NOT use octal literals.
// GOOD
let count = 10;
// GOOD
let price = 10.0;
let price = 10.00;
// GOOD
let num = 0xA2;
// GOOD
let num = 1e23;
// BAD - Hanging decimal point
let price = 10.;
// BAD - Leading decimal point
let price = .1;
// BAD - Octal (base 8) is deprecated
let num = 010;
-
null
should be used only in the following situations:- To compare against an initialized variable that may or may not have an object value
- To pass into a function where an object is expected
- To return from a function where an object is expected
// GOOD
function getPerson() {
if (condition) {
return new Person("Alex");
} else {
return null;
}
}
// BAD - Testing against an uninitialized variable
let person;
if (person != null) {
doSomething();
}
// BAD - Testing to see if an argument was passed
function doSomething(arg1, arg2, arg3, arg4) {
if (arg4 != null) {
doSomethingElse();
}
}
- Never use
undefined
as a literal. To test if a variable has been defined, use thetypeof
operator.
// GOOD
if (typeof variable == "undefined") {
doSomething();
}
// BAD - Using undefined literal
if (variable == undefined) {
doSomething();
}
- Operators with two operands must be preceded and followed by a single space to make the expression clear.
- When parentheses are used, there should be no white space immediately after the opening paren or immediately before the closing paren.
// GOOD
const found = (values[i] === item);
// GOOD
if (found && (count > 10)) {
doSomething();
}
// BAD - Missing spaces, Extra space after opening paren, Extra space around argument
for ( i=0; i<count; i++) {
process( i );
}
- The opening brace should be on the same line as the containing statement.
- Each property-value pair should be indented one level with the first property appearing on the next line after the opening brace.
- If the value is a function, it should wrap under the property name and should have a blank line both before and after the function.
// GOOD
let object = {
key1: value1,
key2: value2,
func: function() {
doSomething();
},
key3: value3
};
// Bad: Improper indentation, Missing blank lines around function
let object = {
key1: value1,
key2: value2,
func: function() {
doSomething();
}
};
Comments may be used to annotate pieces of code with additional information. The acceptable annotations are:
-
TODO
- Indicates that the code is not yet complete. Information about the next steps should be included. -
HACK
- Indicates that the code is using a shortcut to achieve its results. -
XXX
- Indicates that the code is problematic and should be fixed as soon as possible. -
FIXME
- Indicates that the code is problematic and should be fixed soon. Less important thanXXX
. -
REVIEW
- Indicates that the code needs to be reviewed for potential changes.
- Always use
===
and!==
to avoid type coercion errors. - Blank lines improve readability by setting off sections of code that are logically related. Use as needed.
- A
return
statement with a value should not use parentheses unless they make the return value more obvious in some way. - The
if
class of statements should have the following form:
if (condition) {
statements
} else if (condition) {
statements
} else {
statements
}
- Use soft-tabs with a two space indent.
- Put spaces after
:
in property declarations. - Put spaces before
{
in rule declarations. - Use hex color codes with each letter capitalized
#123ABC
unless usingrgba()
. - Any
$variable
or@mixin
that is used in more than one file should be put inglobals/
. Others should be put at the top of the file where they're used.
/* Good coding style example! */
.styleguide-format {
border: 1px solid #0F0;
color: #000;
background: rgba(0,0,0,0.5);
}
In general, the CSS file organization should use the following format:
css/
├── global/
│ ├── components/
│ │ ├── button.scss
│ │ └── checkbox.scss
│ ├── header.scss
│ └── footer.scss
├── layout/
│ ├── _base.scss
│ └── _login.scss
├── mixins/
│ ├── _font.scss
│ └── _navigator.scss
└── vendor/
└── _media-queries.scss
Use ID and class names that are as short as possible but as long as necessary.
.nav {
/* Instead of .navigation */
}
.author {
/* Instead of .atr */
}
Do not concatenate words and abbreviations in selectors by any characters other than hyphens.
.demo-image {
/* Instead of .demoimage or .demo_image */
}
ID names should be in lowerCamelCase (although, as mentioned above, this should be AVOIDED for CSS styling hooks)
#pageContainer {
}
Class names should be in lowercase, with words separated by hyphens. (as mentioned above).
.my-class-name {
}
HTML elements should be in all lowercase.
body,
div {
/* Instead of BODY, DIV */
}
- As a rule of thumb, do NOT nest further than 3 levels deep.
- If you find yourself going further, consider reorganizing your rules (specificity needed or the layout of the nesting).
- Unit-less line-height is preferred because it does not inherit a percentage value of its parent element, but instead is based on a multiplier of the font-size.
- Long, comma-separated property values (such as collections of gradients or shadows) should be arranged across multiple lines.
- Avoid using ID selectors.
- Include a snippet of HTML in a CSS comment for situations where it would be useful for a developer to know exactly how a chunk of CSS applies to some HTML.
- Comments that refer to selector blocks should be on a separate line immediately before the block to which they refer.
/* Comment about this selector block. */
selector {
property: value; /* Comment about this property-value pair. */
}
- Multiple selectors should each be on a single line, with no space after each comma.
selector1,
selector2,
selector3,
selector4 {
}
- Broad selectors allow us to be efficient, yet can have adverse consequences if not tested. Location-specific selectors can save us time, but will quickly lead to a cluttered stylesheet. Exercise your best judgement.
- Always avoid "Magic Numbers".
- These are numbers that are used as quick fixes on a one-off basis.
- Just because if works for your one scenario, doesn't mean it will work for all examples and permutations.
/*
Magic Number example.
AVOID this whenever possible!
*/
.box {
margin-top: 37px;
}
- Strive to only include selectors that include semantics.
- A
span
ordiv
holds none. - A
heading
has some. - A class defined on an element has plenty.
- A
Break to a new line if the tag contains another element.
<!-- GOOD -->
<p>
This is a
<a href="#">link</a>.
</p>
<!-- BAD - All tags are grouped on a single line -->
<p>This is a <a href="#">link</a>.</p>
In the above Good example note the period needs to be right after the anchor tag here because we do not want an extra space when the browser renders the HTML.
This also makes sense:
<h2>June 16<sup>th</sup></h2>
because there shouldn’t be a space or possibility of line break between the date and its ordinal indicator.
Escape the following characters with HTML entity encoding to prevent context switching into any execution context, such as script, style, or event handlers.
& --> &
< --> <
> --> >
" --> "
' --> '
/ --> /
Use Smart Quotes when appropriate.
- Maintainable Javascript by Nicholas C. Zakas
- Code Conventions for the JavaScript Programming Language by Douglas Crockford
- AirBnB Javascript Styleguide
- GitHub CSS Styleguide
- Google HTML/CSS Style Guide
- Principles of writing consistent, idiomatic CSS by Nicolas Gallagher
- My HTML/CSS coding style by Harry Roberts
- Improving Code Readability With CSS Styleguides by Vitaly Friedman
- ThinkUp Code Style Guide: CSS by Gina Trapani
- Wordpress CSS Coding Standards Handbook
- CSS Style Guides by Chris Coyier