A Front-End Philosophy

My approach to coding, circa March 2014

When I built the prototype homepage for PBS Learning Media, I did not do it in a vacuum.

Indeed, there was a whole offshore development team, plus another front-end person in the office in Arlington. This meant it became necessary to document my work, to articulate the philosophy with which I'd built the site.

That document is somewhere deep in the PBS intranet, and sharing it would likely result in me being shot by someone from Downton Abbey or Sesame Street. I do, however, have my original notes to hand, and I thought I might share them with the world, once they've been sanitised.

LESS

Create a separate .less file for each major section the the site. Currently, there is a static/station/less/homepage.less file, which compiles into static/station/css/homepage.css. Other files should be created in the static/station/less folder.

The first file to be imported is base.less, which sets up the site-wide styles and objects for the site. We'll use homepage as an example for ongoing development.

@import 'base'; 

This is followed by the objects and styles created specifically for the homepage section:

@import 'homepage-global';  
@import 'objects/register-box';
@import 'objects/carousel';
@import 'objects/featured-items';
@import 'objects/news-stories';

LESS Folder Structure

Before we go any further, let's look at the structure of the less folder:

/less
 + /fonts
 + /modules
 + /objects
 + /partials
 + /vendor
   base.less
   custom-bootstrap-variables.less
   custom-bootstrap.less
   homepage-global.less
   homepage.less
   main.less

Vendor code such as the less files from Bootstrap sit in the /vendor folder. In the case of Bootstrap, it has multiple .less files, and so gets its own subdirectory. Single files could sit in the /vendor root.

The fonts folder, surprisingly, contains the font files.

All of the .less files are split into three folders: modules, objects and partials. The modules folder contains .less files that do not output any CSS (mixins, variables and helper functions). Partials are the meat of the CSS - typographic rules, responsive styles and so forth. Finally, the objects folder contains reusable objects, such as the site navigation, header, search bar and footer.

Bootstrap 3

The site uses Bootstrap 3. To keep it modular (and therefore easy to maintain), Bootstrap's .less files are used, rather than rather than a compiled CSS file.

/less
 + /vendor
   + /bootstrap
     alerts.less
     badges.less
     bootstrap.less
     ...
     variables.less
     wells.less

Bootstrap is large and somewhat unwieldy. Rather than @import 'vendors/bootstrap/bootstrap', the booststrap.less file has been duplicated to /less/custom-bootstrap.less and it has been edited. Any Bootstrap component not yet needed in the site has been commented out and is not imported. This reduces the size of the generated CSS files considerably. If, for example, Bootstrap tables need to be added to the site, just uncomment the line //@import "vendor/bootstrap/tables.less"; and recompile the CSS files.

When upgrading Bootstrap, compare this file to the upgraded bootstrap.less in the vendors/bootstrap folder, and make sure any changes are reflected in custom-bootstrap.less.

There is a custom implementation of the vendor/bootstrap/variables.less file, custom-bootstrap-variables.less. Certain Bootstrap defaults required overriding to match the design. Rather than duplicate the entire file, only the sections being altered have been copied into custom-bootstrap-variables. Since Bootstrap's original variables file is over 800 lines long, this makes spotting changes easier, and should ease the difficulty of upgrading.

At the time of writing, three section have been placed in the custom file: Media query breakpoints, Grid System variables and Container Sizes.

OOCSS and Modularity

With such a large site, reusability, portability and modularity of existing code is important. This is why the code has been written following (loosely) Object-Oriented CSS (OOCSS) concepts.

The site is split into objects (a header, a footer, a carousel) and these objects are split into their own files. This way, they can be easily added or removed from a page's CSS as necessary using LESS @includes.

OOCSS requires the use of class names rather than specific selectors. Consider a navigation:

<nav role="navigation">
    <ul>
        <li>
            <a href="#">Nav Item</a>
        </li>
        <li>
            <a href="#">Nav Item</a>
        </li>
        <li>
            <a href="#">Nav Item</a>
            <ul>
                <li>
                    <a href="#">SubNav Item</a>
                </li>
                <li>
                    <a href="#">SubNav Item</a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

These elements can be targeted like so:

nav[role="navigation"] {...}           /* 1 */
nav[role="navigation"] ul {...}        /* 2 */
nav[role="navigation"] li {...}        /* 3 */
nav[role="navigation"] a {...}         /* 4 */

Item 1 is fine, but items 2, 3 and 4 will be applied to the sub-nav, which can produce undesired results. There is, of course, specificity:

nav[role="navigation"] ul ul {...}     /* 5 */
nav[role="navigation"] ul ul li {...}  /* 6 */
nav[role="navigation"] ul ul a {...}   /* 7 */

Things are getting complicated. In the pursuit of 'clean' markup, this kind of spaghetti CSS is often a side-effect. Revisiting this code months later, it can take a while to understand what is happening, and it is not clear what the effects of changing nav[role="navigation"] ul {...} might be, until the cascade is fully understood.

Consider the following:

<nav role="navigation" class="main-nav">
    <ul class="nav-list">
        <li class="nav-item">
            <a href="#" class="nav-link">Nav Item</a>
        </li>
        <li class="nav-item">
            <a href="#" class="`link">Nav Item</a>
        </li>
        <li class="nav-item">
            <a href="#" class="nav-link">Nav Item</a>
            <ul class="sub-nav">
                <li class="sub-nav-item">
                    <a href="#" class="main-nav__sub-menu-link">SubNav Item</a>
                </li>
                <li class="main-nav__sub-menu-item">
                    <a href="#" class="main-nav__sub-menu-link">SubNav Item</a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

At first glance, this looks messier. But consider this CSS:

.main-nav {...}        /* 1 */
.nav-list {...}        /* 2 */
.nav-item {...}        /* 3 */
.nav-link {...}        /* 4 */
.sub-nav {...}         /* 5 */
.sub-nav-item {...}    /* 6 */
.sub-nav-link {...}    /* 7 */

Not only is the CSS easier to parse, but a change to .main-nav cannot affect .sub-nav like a change to nav[role="navigation"] ul can affect nav[role="navigation"] ul ul as we have sidestepped CSS inheritance. Individual classes do not have to be declared for every level: if submenu links are the same as main menu links then use .nav-link for both and don't declare .sub-nav-link.

As a bonus, we can use these styles anywhere in the site without worrying about the HTML markup around the targeted element causing strangeness. Even better, it doesn't matter what kind of element we use, which allows for flexibility when changing the HTML. We are free to choose the best HTML structure for the job in hand without altering our CSS.

OOCSS Does not require a class to be attached to every HTML element. Instead, you should attach a class to any element you wish to style.Always prefer a new class rather than a complex selector.

Use Verbose HTML

It is a measure of pride for developers that we can use very little markup to achieve our goals. A side-effect of this methodology is convoluted CSS. OOCSS works better with extra code, which makes separating objects from the overall page markup a more achievable task. Once outside frameworks and plugins with their own JS and CSS are added, this becomes much more important.

EXAMPLE: BOOTSTRAP GRIDS
Consider Bootstrap grids. It is possible to work like so:

<div class="container-fluid">
    <div class="col-6">
        <h1>Title</h1>
        <p>Text</p>
    </div>
    <div class="col-6">
        <h2>Subtitle</h2>
        <p>More Text</p>
</div>
</div>

The columns can be styled directly. Since .col-x has a margin-left and margin-right value, and is floated, it immediately constrains the CSS that can be used to style it without breaking the layout. Should position: be used, the grid will break. If the column's content needs a margin or padding, allowances must be made for the inherited grid styles. The layout may break, and this is more likely since the site has multiple breakpoints. Worse, if the grid behaviour changes from one version to the next, upgrading becomes a more time-consuming endeavour.

Instead, consider this code:

<div class="container-fluid">
    <div class="col-6">
        <div class="some-content">
            <h1>Title</h1>
            <p>Text</p>
        </div>
    </div>
    <div class="col-6">
        <div class="some-content">
            <h2>Subtitle</h2>
            <p>More Text</p>
        </div>
    </div>
</div>

Yes, there is more markup. It was necessary to separate the grid from the content. There are now more options available when styling the content, and changes can be made in the knowledge that if Bootstrap changes .col-x to be an inline-block, or adopts flexbox, only the grid will require debugging.

More Markup = Less CSS Debugging

A little more HTML is a small price to pay for easier maintainability and greater modularity. Only other developers will see the HTML markup, but it's likely a lot ot time will be spent debugging CSS. Knowing that one element cannot have an unexpected effect on another makes that job much easier.

This predictability means that if .col-x wraps to another line, the issue is with the column, not its contents. This means less time in the Element Inspector and a faster bug fix. Verbosity in HTML code is a small price to pay for predictability.

A Note on BEM Notation

A problem with using classes throughout HTML markup is that eventually, a multiple-developer team will accidentally reuse an existing class. This is not fun.

BEM notation is a stylistic choice and stands for Block Element Modifier. It is a verbose method of naming classes to avoid class name conflicts and make clear the structure of the object.

Consider our previous example:

<nav role="navigation" class="main-nav">
    <ul class="nav-list">
        <li class="nav-item">
            <a href="#" class="nav-link">Nav Item</a>
        </li>
        <li class="nav-item">
            <a href="#" class="nav-link">Nav Item</a>
        </li>
        <li class="nav-item">
            <a href="#" class="nav-link">Nav Item</a>
            <ul class="sub-nav">
                <li class="sub-nav-item">
                    <a href="#" class="main-nav__sub-menu-link">SubNav Item</a>
                </li>
                <li class="main-nav__sub-menu-item">
                    <a href="#" class="main-nav__sub-menu-link">SubNav Item</a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

It uses this CSS:

.main-nav {...}        /* 1 */
.nav-list {...}        /* 2 */
.nav-item {...}        /* 3 */
.nav-link {...}        /* 4 */
.sub-nav {...}         /* 5 */
.sub-nav-item {...}    /* 6 */
.sub-nav-link {...}    /* 7 */

These are some generic class names. .nav-link may well be used elsewhere in error.

<nav role="navigation" class="main-nav">
    <ul class="main-nav__list">
        <li class="main-nav__item">
            <a href="#" class="main-nav__link">Nav Item</a>
        </li>
        <li class="main-nav__item">
            <a href="#" class="main-nav__link">Nav Item</a>
        </li>
        <li class="main-nav__item">
            <a href="#" class="main-nav__link">Nav Item</a>
            <ul class="sub-menu">
                <li class="sub-menu__item">
                    <a href="#" class="sub-menu__link">SubNav Item</a>
                </li>
                <li class="sub-menu__item">
                    <a href="#" class="sub-menu__link">SubNav Item</a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

At first glance, this looks messier. But consider this CSS:

.main-nav {...}        /* 1 */
.main-nav__list {...}  /* 2 */
.main-nav__item {...}  /* 3 */
.main-nav__link {...}  /* 4 */
.sub-menu {...}        /* 5 */
.sub-menu__item {...}  /* 6 */
.sub-menu__link {...}  /* 7 */

BEM denotes the structure of the block, but the block can be placed anywhere in a page's HTML. Nesting of blocks is not necessary. In this example, the .sub-menu is a separate block to the .main-nav. It may be tempting to use .main-nav__sub-menu and then .main-nav__sub-menu__item, but this is overly specific.

This defeats the goal of BEM, which is to clarify the structure of self-contained objects. A .block such as .sub-menu should not require a container like .main-nav to exist. The .sub-nav styling may be used on any page inside any other HTML is we decouple it from .main-nav.

HTML denotes the hierarchy of the page, the BEM notation denotes the hierarchy of the object.

Not all elements inside an object need to be written in BEM. If the style of the element is unique to that element, use .block__element. If there is a pre-existing .element elsewhere that will work, use that instead.

As a reminder, BEM is a stylistic choice. It is not mandatory, but understanding it will make the prototype easier to follow.

JavaScript and Progressive Enhancement

The prototype has been built using a philosophy of progressive enhancement: in short, if JavaScript fails to load, contains an error, or is disabled, every function enhanced by JavaScript will still work. This is the ESCALATORS VS ELEVATORS principle: if an elevator breaks, it cannot be used. If an escalator breaks, it can be used as stairs.

The base.html template has a class of .no-js attached to the body. In the JS code, this is removed and replaced with .js-active and this provides three layers of control: styles only for non-JS pages (.no-js .selector), only for those with JS active (.js-active .selector), or for both (.selector).

Classes for CSS, IDs for JavaScript

99% of the time, it is cleaner to reference a class selector in CSS. This allows for modularity and reusability. Combined with BEM, the CSS can be specific without falling into the specificity trap. An ID is 255 times more specific than a class.

However, when using JavaScript to manipulate the DOM, it is more efficient to target a specific target by ID. To this end, ID's are added to objects that will be targeted, but they are styled in CSS using classes. The ID is only for targeting.

If a target's appearance needs to change, it is best to swap classes whenever possible rather than rely on injecting inline CSS. Should the styles rely on computed values, inline CSS in the JavaScript code is unavoidable.

JavaScript-only Classes

To make debugging easier and to provide a clear separation between inserted by JavaScript and those that belong in the HTML markup, all inserted classes are prefixed 'js-'. For example, when toggleElement() opens a previously hidden element, it adds a class of 'js-open'; 'js-active' is appended to the tag upon successful load of the JS. This allows us to move JS-injected CSS into the LESS files and out of the .js files whenever viable.

Transitions: CSS vs. JS

It is better to use a CSS transform than a JavaScript animation: the CSS transitions are usually hardware-accelerated, and most JS animation requires significantly more screen repainting (and therefore processor power) than a transition. Sometimes, these are unavoidable.

CASE STUDY 1: PAGES

There are multiple 'pages' in the base template. Only one is visible at a time. Buttons can be created that allow switching between pages. By using 'opacity: 0;' and 'opacity: 1;', attaching a transition to the .page, and swapping classes, we get a fast, smooth, full-screen transition between pages. Should a user be on an older browser, they still get the same swift response, but they lose the animated effect.

CASE STUDY 2: SEARCH FILTERS

There are optional filters attached to the search bar. When these are revealed, the rest of the page moves down to make space, using jQuery slideUp() and slideDown(). The CSS transform requires an explicit height in order to work, SlideDown() does not. It was undesirable to set the height of the filter holder in CSS: it needs to be different at each breakpoint, and would require changing whenever the contents or their styling changes.

WHY USE ONE OVER THE OTHER?

The other factor is one of UX: When we change an entire screen, the transition is a nice effect, but since the change is so large, it is not necessary for the user to understand what has happened. Conversely, when changing what might be a small area of the screen, the animation helps draw the eye to the change. This is why the reveal of the search criteria must be animated, but the page change does not require it.

Conclusion

If the UX requires animation, use transitions (provided they will work), or JS animations if they won't.

Postscript

That's all there is of my documentation that I can publish. It's certainly not a complete philosophy and it's definitely not one-size-fits-all, but perhaps it might set your mind going and launch you to even greater heights. Or not.

Contact Me

Ask me a question via one of the following social networks: