Our Markup & CSS Approach

Cantilever’s frontend development approach is minimalist. We like to wait for new technologies to be tried and tested before jumping in. We are often late to bandwagons, but always avoid fads.

We want our code to be usable by anyone. Part of digital hospitality is accessibility. All code should be written with accessibility in mind from the start. Don’t push it off until “we have more time.” That point has never actually come...

We like “native code.” We want to write code that is visually close to what the browser will actually read and parse. We like to be “close to the metal” so that we have the greatest possible understanding of what we are asking the browser to render, and we leave ourselves less vulnerable to bugs.


Markup should be written in the ideal semantic structure for the content of the page. If you look at the HTML of a given template/object as an XML document, it should pretty much make sense.

CSS should be subservient to markup. Markup should not become less semantic because of a CSS concern. Write clean semantic markup first, and then write CSS around it.

We are writing more comprehensive docs to capture our HTML approach. For now we recommend this writeup on semantic HTML and why it’s important:


Markup should utilize ARIA attributes in line with modern accessibility best practices. As a rule of thumb, if a certain piece of functionality can be accomplished using a valid ARIA attribute rather than a class or ID, use it.

The goal of ARIA is to create a standardized API for markup to provide screen readers or other non-visual technology information about the nature and state of HTML elements.


In 2018 we switched from an OOCSS methodology to a new one based on an ITCSS architecture.

Before you start, review this general background on ITCSS.

We use BEM syntax to write our classes. BEM allows us to clearly scope styles to the DOM elements which need them. The style leads to somewhat more wordy classes, and in some cases, code duplication, but we prefer the structure and predictability of it. BEM can be used with any CSS architecture, so do not confuse BEM with ITCSS – one is syntactical, the other is architectural.

Core Principles

  • Write totally vanilla CSS if possible. Most sites still benefit from the use of a preprocessor like LESS (our standard), but we are actively looking to drop this dependency. Even if LESS is in use, default to using native CSS features rather than LESS features unless there is a substantial advantage.
  • CSS should be minimal and DRY.
  • To apply styles to markup, default to adding a BEM-style class to flag specific elements which need styles. Then, use direct descendent selectors, data attribute selectors, sibling selectors, etc when adding a class would be wordy and unnecessary.
  • Abstract styles whenever possible. If you repeat the same block of CSS twice, it‘s probably worth abstracting out of each location and into a new component, utility, or element style. For instance, if your carousel object and your quiz object both include identical left/right arrows, abstract out the arrows to be their own component. If you notice that the vast majority of a tags on the site are underlined, abstract that rule out into elements/a.less.
  • ... but don’t be afraid of one-off, specific styles when they are indeed one-off. There is no reason to create an abstract pattern for a one-off template. If it‘s specific to one use-case, just write the styles for that specific use-case, and abstract them out later if needed.
  • Make heavy use of CSS custom properties to DRY up your code and also provide a simple API for other objects/templates to affect the look of an object included within them.

Code Structure

We are typically still using LESS as a preprocessor and compiling down to a single styles.css file. That will likely shift in 2020 with innovations in CSS and post-processing being more of a fluid way to augment it. For now...

  • source
    • less
      • 01-settings: Global styling like resets
      • 02-tools: Re-usable tokens and variables, mixins, etc.
      • 03-generic: Site-wide styling, such as for the body or html element
      • 04-elements: Styles to apply to any HTML element of a given type, like `a` or `article`. Many basic styles of this type go in the reset though.
      • 05-objects: Styles associated with specific BEM-namespaced HTML object patterns which can be used many places within a page and also nested within other objects, components, or templates.
      • 06-components: Styles associated with patterns that cannot be encapsulated within other patterns, such as a Hero.
      • 07-templates: One-off styles associated with full page templates, like `home` or `404`.
      • 08-utilities: Overrides and accessibility utility classes

Please note: Our initial ITCSS structure used only a templates folder rather than having both components and templates. You will see older projects with this method.

Special Template Class Structure

To help quickly distinguish between template-level classes (which apply to an entire page and are added to the body tag) and objects, we have a convention where template classes are preceded with t__. For example .t__people would be the class applied to the body on the People page, but .people could be the name of an object meant to show a set of people. In fact, the object might even appear within the people page, so you may have some styling like...

.t__people {
  .people {
     // On the people page, make the people object’s images larger
     --people__image-max-width: 33%;

Nuts & Bolts Style Rules

  • Two-space indentation
  • Avoid IDs except for accessibility purposes
  • BEM syntax
  • Do not differentiate between classes that connect to JS and those that don’t. A class can signify styling, behavior, or both.
  • Use CSS grid to lay out a page, not markup.
  • Use ARIA attributes whenever possible.
  • Allow, but do not overuse, special modifier and utility classes to support corner cases.
  • Double quotes
  • Be as ambitious as you can with new CSS features, within your browser requirements
  • :: for pseudoelements
  • Carriage return after comma in selectors

The linter will bother you about most of this stuff.