Back in Chapter 3, Implementing Received Wisdom, I recognised the benefits that the BEM approach of naming CSS selectors gave us. Naming a block and then naming any child elements in relation to that block created a namespace for the child elements.
Namespacing the CSS of a module creates a form of isolation. By preventing name collisions with other elements, chunks of CSS can be more easily moved from one environment to another (from prototype to production for example). It's also far less likely that a change of styles on one selector would inadvertently affect another.
There are a number of other approaches to solve the name collision problem. For example, if you are building an application with the popular React (https://facebook.github.io/react/) framework, consider Radium (https://github.com/FormidableLabs/radium) which will inline the styles for each node so you can effectively serve no CSS at all. Naturally, there are trade-offs such as a lack of caching and no way to add reset styles but it it certainly solves the issue at hand. In addition, when not building with React, consider CSS Modules (https://github.com/css-modules/css-modules). While requiring more involved tooling than ECSS it means you could forgo having to think about naming things altogether as it creates CSS scoped for you. Read more about that here (https://medium.com/seek-ui-engineering/the-end-of-global-css-90d2a4a06284).
ECSS takes the notion of selector namespacing and turns it up to 11 (https://en.wikipedia.org/wiki/Up_to_eleven). Selectors are effectively namespaced in two ways:
Let's look at these in more detail. The micro namespace is a simple 2–3 letter namespace for each module. Building a shopping cart? Try.
sc-
as your micro namespace. Building the next version of that same shopping cart? That'll be.
sc2-
then. It's just enough to isolate your component styles and allow the styles to be more self documenting. Let's consider a more involved example.
For example, suppose the micro namespace was being used to convey the parent or origin of the logic that created it. Back to our shopping cart example. We might have a file called ShoppingCart.php
that contains all the logic relating to our imaginary shopping cart. We could therefore use sc-
as an abbreviation of that file name so we know that any elements that begin with that namespace relate to the shopping cart and are rendered by that related file.
In this case, we would then have selectors like:
sc-Title
: The title of the shopping cartsc-RemoveBtn
: A button that removes an item from the shopping cartHere the selectors are quite compact-aesthetically pleasing if a selector can even be described in that way. However, suppose we have a shopping cart which can live in multiple contexts. A mini cart view and a full page view. In that instance we might decide to use the micro namespace to convey context. For example:
mc-ShoppingCart_Title
: The title of the shopping cart, generated by the file ShoppingCart
when in the mini cart view/context.mc-ShoppingCart_RemoveBtn
: The remove button of the shopping cart, generated by the file ShoppingCart
when in the mini cart view/context.Neither of these is the one true way. Part of ECSS philosophy is that while some core principles are essential, it can adapt to differing needs. Generally speaking, for smaller scale use cases, the former approach is fine. However, despite the comparative verbosity of the selectors in the second approach, it is the most resilient and self-documenting. With the second approach you know context, the file that generated the selector (and therefore the module it belongs to) and the element it relates to.
There is more specific information about applying ECSS conventions to web applications and visual modules in Chapter 7, Applying ECSS to Your Website or Application.
As namespaced modules and components are almost guaranteed to not leak into one another, it makes it incredibly easy to build out and iterate on new designs. It affords a hitherto un-thinkable blanket of impunity. Just make a new partial file for the thing you are building, assign a suitable micro-namespace and module name and write your styles, confident in the fact you won't be adversely affecting anything you don't want to. If the new thing you are building doesn't work out, you can just delete the partial file, also confident that you won't be removing the styles for something else. CSS authoring and maintenance confidence - finally!
As our rules are now isolated, it makes the order of rules in a style sheet unimportant. This benefit becomes essential when working on a large-scale project. In these scenarios it is often preferable for partial files to be assembled in any order. With rules isolated from each other, this is simple. With our self-quarantined rules, it makes file globbing of partial styles sheets simple and risk free. With some basic tooling in place you can compile all the CSS partials within a module in one fell swoop like this:
@import "**/*.css";
No more writing @import
statements for every partial in a project and worrying about the order they are in.
We will talk more about file globbing in Chapter 9, Tooling for an ECSS Approach.
As the naming of items is so useful and essential to achieving our goals, the following section documents the naming convention of ECSS in more detail. Think of this like a Haynes manual (https://haynes.co.uk/catalog/manuals-online) for your CSS selectors.
Here's a breakdown of an ECSS selector:
.namespace-ModuleOrComponent_ChildNode-variant {}
To illustrate the separate sections, here is the anatomy of that selector with the sections delineated with square brackets:
.[namespace][-ModuleOrComponent][_ChildNode][-variant]
With more than a couple of developers on a project I'd recommended that commits to a codebase are automatically rejected that don't follow the ECSS naming pattern. Some information on necessary tooling to facilitate this is covered in Chapter 9, Tooling for an ECSS Approach.
Let's go back over the various parts of the ECSS selector and the allowed character types:
Namespace
: This is a required part of every selector. The micro-namespace should be all lowercase/train-case. It is typically an abbreviation to denote context or originating logic.Module or Component
: This is a upper camel case/pascal case. It should always be preceded by a hyphen character (-
).ChildNode
: This is an optional section of the selector. It should be upper camel case/pascal case and preceded by an underscore (_
).Variant
: This is a further optional section of the selector. It should be written all lowercase/train-case.Using this syntax, each part of a class name can be logically discerned from another. More information on what these sections are and how they should be employed follows:
As discussed above, the first part of a HTML class/CSS selector is the micro namespace (all lowercase/train-case). The namespace is used to prevent collisions and provide some soft isolation for easier maintenance of rules.
This is the visual module or piece of logic that created the selector. It should be written in upper camel case. I've seen ECSS applied to great effect when the module or component directly references the name of the file that creates it. For example, a file called CallOuts.js
could have a selector such as sw-CallOuts
(the sw-
micro namespace here used to denote it would be used Site Wide). This removes any ambiguity for future developers as to the origin point of this element.
If something UpperCamelCase is preceded by an underscore (_
) it is a child node of a module or component.
For example:
.sc-Item_Header {}
Here, _Header
is indicating that this node is the Header
child node of the Item
module or component that belongs to the sc
namespace (and if it it were a component, that namespace could indicate the parent module).
If something is all lowercase/train-case and not the first part of a class name it is a variant flag. The variant flag is reserved for eventualities where many variants of a selector need to be referenced. Suppose we have a module that needs to display a different background image depending upon what category number has been assigned to it. We might use the variant indicator like this:
.sc-Item_Header-bg1 {} /* Image for category 1 */. sc-Item_Header-bg2 {} /* Image for category 2 */. sc-Item_Header-bg3 {} /* Image for category 3 */
Here the -bg3
part of the selector indicates that this.
sc-Item_Header
is the category 3 version (and can therefore have an appropriate style assigned).
Our previous example indicates a perfect situation where it would be appropriate to use two classes on the element. One to assign default styles and another to set specifics of a variant.
Consider this markup:
<div class="sc-Item_Header sc-Item_Header-bg1"> <!-- Stuff --> </div>
Here we would set the universal styles for the element with sc-Item_Header
and then the styles specific to the variant with sc-Item_Header-bg1
. There's nothing revolutionary about this approach, I'm just documenting it here to make it clear there is nothing in the ECSS approach that precludes this practice.
3.129.24.180