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 withis-
(e.g.is-disabled
,is-selected
).
Template Code
- Use haml
- Prefer standard Rails view helpers over libraries like
simple_form
orformtastic
CSS
-
Use Sass with Sass syntax
-
By default, use dashes for CSS classes instead of underscores:
my-class
instead ofmy_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
- always prefix partials (= files meant for @importing) with an underscore
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
- get your icon font from fontello.com
- use fontello-rails-converter to make the icon font ready for the Rails asset pipeline & 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">
/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
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
@
overthis
- leave out the dot:
@name
instaead of@.name
that
- use fat arrow
myFunction = (argument) =>
overthat = 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)