No matter what back end technology is used to generate a website, when it gets rendered to a page it always becomes HTML, CSS, and JavaScript when displayed in browser. As such, a common set of best practices can be employed to ensure that what the user gets is as good as it can be, regardless of the device or method they choose to browse the site with. The methodology described below presents a content focused, component based, semantic, and accessible approach for building web sites. Designing this way is challenging, and requires a different approach than traditional desktop-sized Photoshop documents. That will be covered in other sections of this document, but one of the best ways to design this way is in the browser using Style Prototypes.
The core of every website is the underlying markup that, through styling and interaction, gets transformed into a web experience. This underlying code is the heart and soul of every site and should be treated as such. By properly utilizing HTML5 semantic markup and ensuring content is accessible and properly marked up with Resource Description Framework in Attributes (RDFa), websites will be able to achieve better search engine optimization (SEO) and ensure that the content will be highly available no matter the accessing system and last long in to the future.
When developing websites, HTML semantic tags should be used and the HTML5 standard should be utilized. Any elements that are obsolete or deprecated as of HTML5 should not be used. For a complete listing of available HTML elements and their defined meaning, see the Web Platform Elements Reference. The proper semantics for each element should always be used, for instance, the <table>
element should only be used to mark up tabular data, never for layout or lists. Elements designed for style, such as <b>
and <center>
should never be used and should be done through styling instead. Should confusion arise as to exactly how to use a given element, or if its definition is not clear, HTML5 Doctor is an excellent supplementary resource to Web Platform. If support is needed for browsers that do not natively implement all semantic elements, the HTML5 Shiv should be conditionally made available.
The accessibility of a web site's content, including its source order without any applied CSS or JavaScript, must be taken into account. Web Platform's Accessibility Basics article is a good introduction to accessibility for those unfamiliar with them. During development, websites should be checked with a screen reader to ensure they are easy to use. All developers using computers running OS X or have access to iOS device have access to VoiceOver as it is installed on both operating systems. Another popular screen reading application, especially for Windows machines, is JAWS, although that is proprietary software and fairly expensive at that. A final resource for testing accessibility is to access a website using the Lynx Browser. As a text-only browser, it will strip out styling and interactions, leaving raw content in the order it is presented. As an added bonus, this is also a good analogue to how crawlers and search engines view a given page.
Resource Description Framework in Attributes, more commonly known as RDFa, is an extension to HTML5 allowing for robust markup of objects in HTML, including items such as people, places, events, recipes, and reviews. They are used to precisely describe these objects, mostly to machines, by attaching metadata about each object as attributes. The most common immediate use for this metadata is in use by search engines and social networks, amongst others, allowing their crawlers to understand exactly what is on any given page. This, in turn, allows them to provide more accurate and higher ranked information about each piece of content. A useful side effect of marking up all content with RDFa information, especially when using a Content Management System (CMS), is that it ensures that all of the relevant metadata is readily available in individual chunks and thus becomes fine-grain points of control for each piece of content that can be syndicated outside of pure markup.
When building responsive websites, for the time being, a non-standard meta tag needs to be used in order to tell browsers how to react. This is colloquy known as a "viewport tag". While there are many options that can go in to the viewport tag, the tag, in its entirety, that should be used is as follows:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
There is currently a CSS Device Adaptation development specification in the works which, when done, will move this from a markup tag to a CSS directive known as the @viewport
directive. Currently, Internet Explorer 10 and up, including on mobile devices, does not use the viewport meta tag, but rather the viewport CSS directive, so both should be included on all projects.
If markup is the skeleton of a website, styling is the funny hat. Through CSS, a stack of content on a page can become flexible and fantastically flourished. With the introduction of CSS3, styling can now include such fanciful additions such as animations, perspective, and even 3D. But with all of this power, and all of this responsibility, a firm structure to create a system of style needs to be in place or else everything can run off of the tracks. Below represents a component based systematic approach to styling.
Due to lack of standards around it, each browser manufacturer creates their own styling for their browser, leaving each browser's default base rendering of elements different. This, of course, causes inconsistencies across browsers that must be fixed. The two most prominent ways of doing this is through either to reset or normalize the appearance of all elements. While normalization has become the go-to method for many projects recently, it can introduce new issues into the cascade. This is the same reason it is recommended to create a base component instead of allowing site-specific base styling to cascade throughout the entire site. Because of this, it is recommended to use the reset method; preferable one that discreetly targets the elements that need resets, fixes the bugs that the common Normalize.css fixes, and adds the correct baselines for HTML5 elements if they are not otherwise recognized (for instance, applying display: block
to the <main>
element).
Components are the primary building block of any interface. They are the bits and bobs that combine to form a cohesive user interface; from menus to messages, pagers to pictures, and everything else in between. If a component can be used in multiple positions in a layout (such as a message or a promoted item), eq.js should be utilized to adapt their layouts (where appropriate) instead of media queries. This will allow a component and its elements to respond appropriately regardless of where they land in a given document flow. If a component will only be used in a single position (such as global headers or footers), media queries can be used. Basically, use what is most appropriate.
Each project should contain a base
component which contains base styling for raw tags (h2
, blockquote
, p
, etc…). The base
component's elements should be named after the tag they style, so basic styling for h2
would provide both an extendable and full class .base--h2
. To apply these styles, create a styled
aspect, providing a .base--STYLED
class. This aspect should have raw elements styled without classes, allowing it to be dropped into any section of a site and provide basic styling. Additional aspects can be created to create different base stylings, such as form
for base form styling.
Layouts are the structure of an interface. Providing structure to pages and components, layouts are responsible for sizing and positioning of their elements. Layouts are generally used for laying out pages and regions within pages. Layouts that include viewport based media queries (width
, height
, etc…) should never be nested inside each other
Aspects are a specific implementation of a component or layout. Aspects can be used as a way to pivot styling of elements if need be or to control implementation-specific styling, such as changing colors in a message component or determining exact sizing of a body element of a layout. It is preferable to use aspects as pivot points rather than to create unique classes for each element as it allows for the reuse of identical elements regardless of the aspect of a component or layout they are used in. Elements should only pivot off of aspects, not off of an aspect-less component or layout.
Elements are the individual pieces that make up a component or layout, each being component or layout specific. Think of them as individual HTML elements (h2
, blockquote
, p
, etc…) in components and regions and items in layouts. When styling an item inside components or layouts, it is strongly discouraged to use raw tag selectors and instead use element classes for each. This is to avoid any potential conflicts, such as would happen if there would be a pager component inside of a slider component (.slider li
and .pager li
). The only exception to this rule is for the base component.
States provide a way to alter how a component, layout, element, or aspect behaves. Common states include active
, open
, and closed
. Because states live in the in-between world of JavaScript and CSS, often changing with interactions from the user, states are controlled by data attributes ([data-state="active"]
) and get attached to the components, layouts, elements, or aspects they are modifying. This provides easy to maintain states on a per-object basis without needing per-object states.
Components and layouts form prefixes for aspects and elements, separated by double dash (--
). Layouts start with an underscore (_
) to distinguish them from components. Aspects are written in all caps (CAPS
) to distinguish them from elements, written in all lowercase (lowercase
). States are applied to the state data attribute (data-state
) and queried using attribute selectors as they have the strong tendency to either be manipulated by JavaScript or be used as a hook for JavaScript. If an object has multiple states, each state should be space (' '
) separated in the data-state
data attribute. A sample document written up using this naming convention could look like the following:
<!-- Article layout with Big Media aspect -->
<div class="_article--BIG-MEDIA">
<!-- Main element of Article layout -->
<article class="_article--main">
<!-- Heading element of Article layout -->
<div class="_article--heading">
<!-- PRIMARY Heading aspect of Typography component -->
<h1 class="typography--PRIMARY-HEADING">Article Title</h1>
</div>
<!-- Media element of Article layout -->
<figure class="_article--media">
<!-- Video components, Full HD aspect -->
<div class="video--FULL-HD">
<!-- Video element of Video component -->
<video class="video--video" />
</div>
</figure>
<!-- Body element of Article layout, Area aspect of Typography component -->
<div class="_article--body typography--AREA">
<h2>Some user entered copy goes here</h2>
<p>Yay Copy!</p>
</div>
</article>
<!-- Secondary element of Article layout -->
<aside class="_article--secondary">
<!-- Popular aspect of Related component -->
<div class="related--POPULAR">
<!-- Heading element of Related component -->
<div class="related--heading">
<!-- Tertiary Heading aspect of Typography component -->
<h2 class="typography--TERTIARY-HEADING">Block Title</h2>
</div>
<!-- Body element of Related component -->
<div class="related--body">
<p>Yay Copy!</p>
</div>
</div>
<aside>
</div>
When writing CSS, use Sass with the Compass authoring framework. When compiling Sass and Compass, only use the Ruby gems to compile them or a tool that calls out to the Ruby gems. The scss
syntax should be used exclusively when writing and sharing Sass. In order to ensure that all environments are the same, the minimum version of Ruby that should be used is 2.0.0
(standard on OS X version 10.9 and up) and all gems should be installed and versions managed by Bundler. When writing a Gemfile, versions should all be specified using the ~>
specifier to ensure that gems stay on the same major and minor versions, making upgrades in minor versions purposeful. Gems should be installed in to a vendor
folder in each project, which should be ignored from version control. In addition to Bundler, there are a number of Compass extensions that should be used as a standard for a variety of needs.
- Singularity - Grid framework
- Breakpoint - Media query framework
- Toolkit - Variety of useful tools for web design and development
- Color Schemer - Advanced color functions
- Modular Scale - Numeric relationships, especially for typography
- Import Once - Changes the way
@import
works so that imports only happen once. Useful for deep nested or shared dependencies
The following should be using the standard Compass configuration for all projects (http_path
is likely to change on a project-to-project basis):
# Set this to the root of your project when deployed:
http_path = "./"
css_dir = "css"
sass_dir = "sass"
images_dir = "images"
javascripts_dir = "js"
fonts_dir = "fonts"
# You can select your preferred output style here (can be overridden via the command line):
# output_style = :compressed
# To enable relative paths to assets via compass helper functions. Uncomment:
relative_assets = true
# To disable debugging comments that display the original location of your selectors. Uncomment:
line_comments = false
Mixins are best used when they don't needlessly duplicate the properties they write. We can do this using placeholder selectors and @extend
. The only properties that should be directly written to the selector calling a mixin should be properties that have been directly altered due to mixin arguments. Any other properties should be extended. All arguments that have default values should have those default values controlled by globally namespaced !default
variables to make overriding those defaults easy and accessible. All mixins that provide extends should also have an $extend
optional argument, ideally as its last argument, also globally defaulted.
Mixins should also be divided up by purpose. While an omni mixin may be easier to write, having smaller mixins will make maintaining and using said mixins, as well as changing discrete parts of a rendered component, easier to do.
Let's take a look at a typical message component mixin as an example of how to re-write it using our mixin/extend pattern.
Sass
// Sass
@mixin message($color, $padding: .25em .5em, $width: 80%) {
box-sizing: border-box;
padding: $padding;
width: $width;
margin: 0 auto;
border: 2px solid $color;
background: mix(white, $color, 25%);
color: mix(black, $color, 25%);
}
.message--STATUS {
@include message(green);
}
.message--WARNING {
@include message(yellow);
}
.message--ERROR {
@include message(red);
}
CSS
/* CSS */
.message--STATUS {
box-sizing: border-box;
padding: .25em .5em;
width: 80%;
margin: 0 auto;
border: 2px solid green;
background: #3f9f3f;
color: #006000;
}
.message--WARNING {
box-sizing: border-box;
padding: .25em .5em;
width: 80%;
margin: 0 auto;
border: 2px solid yellow;
background: #ffff3f;
color: #bfbf00;
}
.message--ERROR {
box-sizing: border-box;
padding: .25em .5em;
width: 80%;
margin: 0 auto;
border: 2px solid red;
background: #ff3f3f;
color: #bf0000;
}
While the single mixin may allow us to easily get the output we want, it does so at the cost of duplicating properties, and thus vastly bloating, our output CSS. This can be remedied almost entirely simply by rewriting our original mixin using our new mixin/extend pattern.
*Sass
// Sass
$message-padding: .25em .5em !default;
$message-width: 80% !default;
$message-extend: true !default;
@mixin message($padding: $message-padding, $width: $message-width, $extend: $message-extend) {
padding: $padding;
width: $width;
@include message--static($extend);
}
@mixin message--static($extend: $message-extend) {
@if $extend == true {
@include dynamic-extend('message') {
@include message--static(false);
}
}
@else {
@include box-sizing(border-box);
margin: 0 auto;
border: 2px solid;
}
}
@mixin message-coloring($color) {
border-color: $color;
background: mix(white, $color, 25%);
color: mix(black, $color, 25%);
}
%message--CORE {
// We include the message-core mixin with $extend == false to force the properties to be written
@include message();
}
.message--STATUS {
@extend %message--CORE;
@include message-coloring(green);
}
.message--WARNING {
@extend %message--CORE;
@include message-coloring(yellow);
}
.message--ERROR {
@extend %message--CORE;
@include message-coloring(red);
}
CSS
/* CSS */
.message--STATUS,
.message--WARNING,
.message--ERROR {
padding: 0.25em 0.5em;
width: 80%;
}
.message--STATUS,
.message--WARNING,
.message--ERROR {
box-sizing: border-box;
margin: 0 auto;
border: 2px solid;
}
.message--STATUS {
border-color: green;
background: #3f9f3f;
color: #006000;
}
.message--WARNING {
border-color: yellow;
background: #ffff3f;
color: #bfbf00;
}
.message--ERROR {
border-color: red;
background: #ff3f3f;
color: #bf0000;
}
While this approach certainly requires more work up front to build the mixins and extendables, it produces much more controlled and succinct output CSS, which is what we're aiming to write.
Sass partials are a way for us to organize our styling knowledge into maintainable and easily grokable chunks. An example North project, including this partial structure, is available in the examples folder.
At the root of our Sass folder is our style.scss
file, which holds the core of our styling, and a partials
directory to hold our various partials.
The partials
directory should be divided up into 4 sub directories, global
, base
, components
, and layouts
. Inside of global
, there should be a folder a piece for variables
, functions
, mixins
, and extendables
, with partials to match. Inside each of those folders should go partials whose content should be made available globally and aren't component specific. For instance, global color and typographic variables, background/text color contrast mixins, ligature extendables, etc…
Base, components, and layouts should be built using the same partial structure, henceforth known as the component partial structure. Each component should have a partial and matching folder, and inside that folder a partial a piece for variables
, functions
, mixins
, and extends
. Each of these partials should hold styling knowledge specific to that component; for instance, variables
could have color variables specific to that component, but the color it is set to should come from the global color partial. An example of this can be seen in the example sass
folder.
The Import Once extension should be utilized to prevent duplication of selectors for extendable classes. Mixins should share their naming convention with the object they are used to style.
It is important to be able to provide enhanced (or degraded) styling based on the progressive enhancement needs of a project. The recommended way to do this is through post-processing a stylesheet with Gulp CSS Target. Using this method will spin multiple stylesheets out of a main one based on defined comment blocks, allowing for easy maintainability. It is recommended that styles are placed in CSS Target comments not extend other selectors as extends are unreliably spun out.
Global, private variables (ones that users should not touch but are needed to hold information for functions or mixins) should start with a capital letter as Sass variables are case sensitive.
What is better than a funny hat? A funny hat that spins. JavaScript adds interactivity to web pages, transforming them from static documents into living ones. JavaScript is a very flexible and powerful tool, but as Stan Lee says, "with great power there must also come -- great responsibility".
A most reasonable approach to JavaScript is the Airbnb JavaScript Style Guide. It provides an excellent set of rules and industry best practices for writing JavaScript, and it should be followed. In addition to the Airbnb guide, the following guidelines should also be followed:
- Functions must be declared before they are used
- All custom scripts should be wrapped in anonymous, self-executing functions with any external dependencies passed in.
(function($) {
var intro = 'Once upon a time, ',
$princess = $('#princess'),
$button = $('#button');
function fairytale(name) {
$princess.html(intro + name + '…');
}
$button.click(function() {
fairytale($princess.text());
});
})(jQuery);
When building site, very often a point will come when a decision must be made as to if a certain JavaScript library, plugin, or framework should be used. In addition to evaluating 3rd party scripts based on the quality of their code and their adherence to the JavaScript Style Guide, the following questions should be considered:
- Is the added functionality worth the weight? A lot can be accomplished with very little in JavaScript. If a minified version of a script is larger than 5KB, seriously consider if everything that it offers is needed or if something smaller and lighter weight can be just as effective. Is a 21.5KB slider library plus the weight of jQuery really worth the precious few bytes and HTTP requests needed for a fast and performant site?
- If the script builds off of another framework, such as a jQuery Plugin, examine the problem and determine if writing a custom solution can be as effective and lighter. Not everything needs to be a jQuery Plugin.
- If a script does not come with a minified version, determine if it can be minified. All scripts should be minified, so if a script being evaluated cannot, that should be taken into consideration.
- Is the script performant? Does it have performance benchmarks? If not, can it be benchmarked? If a script, regardless of weight, slows down a site significantly, its use should be reconsidered.
- Given browser support, is a heavy JavaScript library like jQuery or Dojo needed? Can paired down versions of those libraries be used? Does usage require the full support behind one of these libraries, or can a small DOM/AJAX library such as Chibi be effective?