Frontend Styleguide


Markup


Classes, IDs, etc.

In your CSS

  • Use classes as much as possible
  • Do NOT use IDs (because they are very hard to override, due to specificity issues)
  • Avoid element selectors (a, p) as much as possible (if you use them, child element selectors are preferred > p)

In your Javascript

  • Use IDs like “#js-something” to bind behavior to single, uniqe elements, because IDs are still fastest
  • Use data attributes instead of classes, to bind behavior to sets of elements (e.g. in the markup <div data-behavior="datepicker"> in the JS $('[data-behavior=datepicker]')). Finding element by data attributes is almost as fast as by classes (blogpost) and it creates a nice separation of concerns between behavior and styling.

Exceptions to the rule

  • The only time where both CSS and Javascript are dealing with classes, is for classes that indicate state. These can be either pseudo classes (:focus, :hover) or classes prefixed with is- (e.g. is-disabled, is-selected).

Template Code

  • Use haml
  • Prefer standard Rails view helpers over libraries like simple_form or formtastic

CSS

  • Use Sass with Sass syntax

  • By default, use dashes for CSS classes instead of underscores: my-class instead of my_class


Sass File Structure

  • Naming convention for Sass files:
    • always prefix partials (= files meant for @importing) with an underscore _
    • always use file extension .css.sass
    • => _partial.css.sass

Overview:

/base
  _element_defaults.css.sass
  _colors.css.sass
  _typography.css.sass
  _dimensions.css.sass
  _sprites.css.sass
  _icon_font.css.sass
/modules
  _box.css.sass
  _button.css.sass
  _list.css.sass
/layout
  _header.css.sass
  _footer.css.sass
/controllers
  _posts.css.sass
  _pages.css.sass
/_shame.css.sass

Base

Basic variables, mixins & styles that are the base for all stylesheets.

_element_defaults.css.sass

  • CSS resets (if used)
  • default styles for elements
  • only element selectors
  • use sparingly

example:

strong
  font-weight: bold

_colors.css.sass

  • literal colors
  • mapped colors ($light-background-color, $border-color)

_typography.css.sass

  • font-family
  • font-size variables
  • possibly typography related mixins

_dimensions.css.sass

  • $content-width
  • breakpoints
  • $padding: 10px, $margin: 20px

_sprites.css.sass

  • Compass

_icon_font.css.sass

Layout

Modules

  • file name: singular (‘button’, ‘box’, ‘list’)
  • load order independent (@import 'modules/*')
  • context independent (avoid fixed widths)
  • content independent
  • complete (+ responsive)
  • module should describe a design element NOT a content element

Controllers

Controller specific styles, scoped using class & ID from <body> tag

<body class="controllername" id="controllername_actionname">
Proposed:
/non-modular
/blocks (for composition, complex modules, content specific)

CSS Frameworks

  • Use Compass whereever possible

Bootstrap

good for:

  • admin backends
  • no design requirements

not good for:

  • anything else
Proposed:
Zurb Foundation

Grids

Roll your own


Responsive CSS

  • breakpoint

Javascript

  • Do NOT use JavaScript UNLESS you know what you’re doing!

  • Write your Javascript using Coffeescript

  • dont’t polute the global namespace
  • define functions/classes in app specific namespace window.ShortAppName = {}

Testable JavaScript

  • Aim to write testable JS
    • Coffescript Classes can help with that
  • Integration tests with Cucumber & co are not enough. JS should also be unit testable

Coffeescript

  • avoid curly braces whereever possible
  • prefer and/or over &&/|| for boolean expressions for readability
  • always use Ruby style string interpolation: "foo #{myVariable} bar"

this

  • always prefer @ over this
  • leave out the dot: @name instaead of @.name

that

  • use fat arrow myFunction = (argument) => over that = this constructs
  • except if you need BOTH this & that inside the function

Anonymous funtions

  • omit braces when there are not arguments:

    myFunction = ->
      # ...
    
  • space before function arrow (dashrocket)

    myFunction = (argument) ->
      # ...
    

File structure & loading sequence

Here is an example for a basic file structure for javascript in a Rails project.

Manifest file

// app/assets/javascripts/application.js.coffee

# = require vendor/library
# = require init
# = require_tree modules

Init file

In the init file we set up the app’s namespace in order to not polute the global namspace. Here we also have our central call to $(document).ready() ($ ->). Ideally there shouldn’t be any other $(document).ready() in the codebase.

// app/assets/javascripts/init.js.coffee

window.Namespace = {}

$ ->
  myInstance = new Namespace.MyClass()
  Namespace.initDatepicker()

A nice, modern, modular class:

// app/assets/javascript/modules/my_class.js.coffee

class Namespace.MyClass

Classic jQuery-style Javascript:

// app/assets/javascript/modules/datepicker.js.coffee

Namespace.initDatepicker = ->
  $(`[data-behavior=datepicker`).datepicker()

Variables & Classes

  • Use camel case for variable names: myLittleVariable
  • Prefix variables that contain jQuery objects with $
  • try to avoid too short variable names: use (event) -> instead of (e) ->

  • classes start upper case Class and instances lower case ìnstance = new Class()
  • namespace also starts upper case window.Namespace = {}

jQuery

Event Listeners

  • Minimize number of event listeners for performance reasons
  • Try listening on a appropiate parent element

Example:

<ul id="list">
  <li class="item">...</li>
  <li class="item">...</li>
  <li class="item">...</li>

Instead of this:

$('.item').click ->
  # behavior

Do this:

$('#list').on 'click', '.item', ->
  # behavior

The advantages are that it’s faster, because you have less event listeners to bind etc. and it is more flexible, since you can add new items to list without needing to add new listeners.

Caching jQuery objects

  • cache jQuery objects in variables, if you need to use them multiple times

Do this:

$form = $('selector')
$form.foo()
$form.bar()

instead of:

$('selector').foo()
$('selector').bar()

Rails & JS

Controversial: * gon - you can use it to share global config between Rails & JS, but then it might be overkill - not so much for controller specific data

Proposed: * routes (Rails’ named routes in JS) * i18n (Rails i18n in JS)