From an architectural standpoint, CSS authoring needs more rules and conventions in addition to what's defined in most common CSS styleguides. This document will define these rules. If there's a conflict between the general guidelines and here, these rules take precedence.
Table of contents:
- Section 1: General rules to authoring CSS/Less
- Section 2: Class construction with TCEM
- Section 3: The UI pyramid and scoping
Stop thinking in pages and elements. Think in components, elements and modifiers. Applications are not a series of documents, so don't code according to that old way of thinking. You may have a file named after your "page", what will be called a sub-application from here on out, but all the CSS should not reflect this sub-application.
/* Bad */
.subApp .header {}
/* Good */
.myComponent-header {}
No more nesting selectors! Nesting couples our Less/CSS with our markup, a sign of improper UI engineering. Our CSS selectors should be completely independent of how the DOM is marked-up. If you have to nest, it can only be one level deep and needs to incorporate the direct descendent selector >
. This could be for state changes, like .active > .element
, which is a legitimate need.
/* Bad */
.myComponent .header h2 {}
/* Bad */
.myComponent {
.header {
h2 {}
}
}
/* Good */
.myComponent-header_secondary {}
Strange class syntax? See section 2 about class construction below for more information.
No more mixins. Mixins create a lot of duplicate CSS properties in outputted CSS, so it’s breaking the DRY principle. Mixins give the illusion of clean code, but the code that actually matters, the outputted CSS, is dirty and smelly.
Rather than mixins, use composition. The class that you would want to use as the mixin, you need to add that to the element, then add an additional class that is responsible for the deltas. This is multi-class composition.
<!-- Bad -->
<div class="myCriticalAlert">Something is wrong.</div>
.myCriticalAlert {
.coreAlert();
border-color: red;
background-color: lightred;
}
<!-- Good -->
<div class="coreAlert coreAlert_critical">Something is wrong.</div>
.coreAlert {
/* Stuff */
}
.coreAlert_critical {
border-color: red;
background-color: lightred;
}
Don't override framework/library classes! Don't redeclare a framework or library provided class and modify it directly. This couples component level code with framework level code, breaking our "separation of responsibilities".
Rather, use composition again. Use the framework or library level class on your element, then add a modifying class:
<!-- Bad -->
<div class="col-md-6">I'm special.</div>
.col-md-6 {
padding: 0;
}
<!-- Good -->
<div class="col-md-6 column_tightSpacing">I'm special.</div>
.column_tightSpacing {
padding: 0;
}
The framework and library directories, and everything in them, are to be treated as immutable. These frameworks live outside of the project and are maintained separately, so any modifications will be overwritten at next update.
Follow the principle of least privilege. In other words, if you need a framework component or library widget, don't throw it in the app's global file, place it in the smallest scope possible. So, if you need the someWidget
from the Shoestrap framework, place it in your component's CSS file.
As this component becomes more and more used, you then increase the level of this widget's scope. Only pulling it to the global CSS file if it's used everywhere.
Only use classes to select elements from the DOM for CSS! IDs are okay for JavaScript selectors. Tag-name selectors are too non-specific and over-reaching for both CSS and JavaScript.
/* Bad */
label {
padding: 0;
}
/* Good */
.myComponent-label {
padding: 0;
}
We follow a BEM-like class construction syntax. Read more about BEM here.. We have our own flavor of BEM that is called TCEM (pronounced teesim): Type Component Element Modifier.
The intention is to leverage a naming convention that address the above problems: maintainability, scoping, specificity and communication.
.{type}_{component}-{element}_{modifier} .is{state}
Classes can be used for many things, JS selector, style selector, utility, functional testing … This describes that type.
Choose one of these options:
- Absence of type is reserved for styling (CSS) selectors
js
is reserved for JS selectorstest
is reserved for functional test selectors
This is also known as “block” in the BEM convention. A component represents a portable collection of elements that make up the intended "thing" you're building. A component may also be made of other smaller components. If this collection of elements or components can reasonably exist outside of the current context, it's a component.
This partial is required as it “namespaces” the class and is the root of this idea.
Examples of valid names:
primaryNav
profilePhoto
contactForm
activityTable
Avoid generic single word names. Don't use nav
or filter
as they are not specific enough. Also, notice how multi-word names are camelCased rather than the common dashed-cased. The dashes and underscores are reserved for partial separators.
This will be very commonly used, but technically isn’t required. It describes the element within the component that you intend to style. Examples:
header
container
list
link
footer
Avoid the actual tag name of the element; although there are some exceptions. Don't write -h4
or -p
, but -header
, -footer
or -section
should be fine.
This should be a lesser used option but is important none-the-less. This describes any modifications to the original element. If in most circumstances you have a particular style, but in this one instance you need it to be different, this is where you apply the modifier. This usually reflects the context of the location. Examples:
sidebar
siteFooter
modal
secondary
We should avoid using page names as modifiers as it's too broad and irrespective of actual usage.
This is used for applying stateful styles to elements. These are always prefixed Examples:
isAnimating
isActive
isDisabled
Notice how in the syntax guide at the start of this section, it's prefixed with is*
.
We need to start separating our code into layers (visualize a pyramid):
Think Bootstrap. This is responsible for layout and structure only.
This is responsible for the basic "skin" of your brand's aesthetic personality.
Global code/components specific to this application.
If your application is composed of other applications.
This would be the tip of the pyramid. This is code that is to support the specific components within your section of the app.
You can view these layers as isolations of intent/responsibilities. Neither layer reaches outside of itself, and that makes them all interchangeable.
Most of our work should reside in layer 4. If global changes are needed, they happen in layer 3. If changes are needed in layer 2 or 1, they happen in their respective repos.