The quest for the perfect CSS complexity management technique
Standing at the end of the second decade of the second millennium, and surveying the front end web development scene, scores of frameworks, methodologies, and miscellaneous technical solutions can be seen scurrying over each other, reaching upwards and outwards for the prizes of validation, market share, and architectural elegance. Crowds of developers look on - some cheering for their favourites, others feeling bewildered by the sheer amount of new blood, perpetually holding onto their gold until they have fully studied every investment option.
Webpack holds a gleaming silver sword up to the sun and cries out, while gulp sits beside her on a chariot marked 'node'. He adjusts his armour and wonders a little nervously if they are really on the same side or not. A middle-aged Grunt plods slowly around the arena circumference with tree-trunk limbs, bashing away any lingering obstacles with its buckler.
In the raised centre, a wild-eyed, long-haired, juggernautian React has temporarily floored Angular, and stands tall to strike a pose. A round of chanting swells, in admiration of the gladiator's power and grace. However, a younger, more nimble contender named Vue casually ambles up the slope to where they preside, and prepares to enter the hottest part of the fray with a confident, but unassuming disposition.
Although most theatre-goers have their attention directed towards the centre of the stadium, a rather novel skirmish has developed near the entrance among the gladiatorial animals, and is beginning to draw substantial interest. Atomic, the fox, is biting at the ankles of BEM the bear. The commotion attracts a couple of tigers and a lion. A woman dressed in fashionable robes, standing to your left, notices you watching, and shouts into your ear.
"He'd better be okay! I have 150 gold bet on this bear leaving the stadium with blood still in it's veins!".
Another well-dressed man nearby chimes in. "The fox is no threat." He exclaims loudly. "I have 250 gold on that bear, and I'm not at all worried! It can handle an adversary of any size!"
"I have 5 on the fox.", pouts a boy with shabby hair, turning around with an indignant countenance. "What about you, old man? What do you think of the animals?"
The elderly figure standing on your right looks slightly bewildered and blinks a few times, as if waking from a dream. "It's all Greek to me...", he croaks into the air, while eyeing the horizon. "In my day, we didn't even have animals in the stadium."
CSS: A blank canvas
This brings us to our point of departure.
How can a lone developer, or development team, avoid the emergence of unmanageable or unnecessary complexity when writing CSS for projects of non-trivial size? Where techniques differ, what are the merits of each?
Up until recently, there were no formalised systems to help developers manage CSS complexity. In the past, stylesheet authors were only guided by their own opinions of the CSS specification, and by unofficial, naturally-emergent best practices which were cultivated by their fellows - typically expounded within books, articles, and chatrooms.
Today, several formal CSS organisation systems exist. Most are similarly unofficial. Among them are BEM, OOCSS, SMACSS, MCSS, AMCSS, Atomic CSS, and the irreverently-named, FUN CSS. BEM and Atomic CSS are paticularly interesting studies, as they are among the most popular, (or most infamous, depending on who you ask), and also the most unconventional. BEM is particularly relevant in our enquiry, as it was specifically designed to tame voluminous CSS codebases.
With this in mind, this discourse will examine two types of CSS organisation practice - those categorised as sets of general rules, and those categorised as formalised systems.
Objectively measuring the level of success that arises from the use of any given code organisation methodology, and comparing it against the success of another, is a dubious preoccupation, and is questionable, not least, for the difficulty one would have in attempting to define success-measuring criteria.
For example, one could take timely project completion as a key criteria. That would seem like a good start. But then, what if one of the apps that makes up your dataset is riddled with bugs, and the others are not? Would you penalise the buggy software? How could you do that quantitatively? What constitutes a bug, and which ones should result in the most severe deprecation?
For these reasons, the objective measuring and comparison of the usefulness of architectural paradigms is practically unheard of. It seems that the question regarding CSS organisation methodologies, like many of the questions in programming, will perpetually remain a philosophical one.
Unfortunately, human nature being what it is, when confronted with a philosophical question, there is often a proclivity to accept that there is no definitive answer, which is a conclusion not far from the act of accepting that all solutions are of equal value. There is a proclivity to forsake the subject, and simply accept what everyone else accepts, and do what everyone else is doing.
However, we don't have to resign to occupy this insidious trap. We may not have data with which to form a robust argument, but we do have the next most reliable tool, held readily at our disposal. Lets attempt to apply some carefully constructed deductive reasoning.
The discourse of the remainder of this article will be structured as follows:
- Examining the nature of CSS, and where, as a tool used to style modern web applications, it's strengths and weaknesses lie.
- Defining rules that describe a potentially imprudent use of CSS, particularly in the context of code organisation.
- Defining rules that describe a prudent and effective use of CSS, particularly in the context of code organisation.
- Examining formal CSS organisation systems, (with specific reference to Atomic and BEM) and deducing which systems are most amicable to our definition of a prudent and effective use of CSS.
- Attempting to develop a new system that adheres, in full, to our definition of a prudent and effective use of CSS.
Examining the nature of CSS
The essence of CSS
Consider our initial question.
How can a lone developer, or development team, avoid the emergence of unmanageable or unnecessary complexity when writing CSS for projects of non-trivial size?
If we are to address this question with pertinence, we should first take a moment to reflect on the true nature of CSS. This may seem like a tedious detour, but acknowledging the language's raison d'etre, general shape as a solution, and a few of it's idiosyncratic gnarls will provide a good foundation for arguments to be built upon. Let's quickly reaffirm our understanding of the language by touching upon three questions that a CSS newcomer would be likely to ask.
- What is it?
- What does CSS stand for?
- What purpose was it designed to fulfil?
Succinct answers to basic questions
- It is a declarative, human-and-computer-readable language, that is utilised in the present day, by software developers, to express the aesthetic presentation of webpages. (As a newcomer would hope for, this is a general defintion, not an academic defintion. A definition of this nature is more pragmatic for our purposes.)
- CSS stands for "Cascading style sheets". The word "style" is an allusion to the language's involvement in establishing aesthetics. The word "sheets" is an allusion to the fact that the language appears within (historically) long, structurally flat, plaintext files. The word "cascading" is an allusion to details within the specification which define an elegant styling-instruction application mechanism which allows developers to target specific sets of elements with a given set of styles, and then recursively overwrite, or target increasingly more specific sets of elements, using subsequent, cascading instructions.
- Cheifly, to grant web page authors more control over a document's aesthetic appearance. As this requirement was reckoned in more detail, it was clear to the HTML Editorial Review Board that a subsidiary requirement was to move aesthetic, presentational code out of HTML, and into a declarative structure of it's own right. This prevented the pollution of HTML's element manifest; the undesirable situation whereby the inclusion of dozens of new presentational elements and attributes would be necessary to afford the language a lexicon that was sufficiently expressive in the context of the new goal. The decision to segregate presentational code from structural markup would keep the HTML cleaner and easier to read and manage. Notably, the style-to-element mapping mechanism that such a system necessitated would also grant web page authors the additional ability of being able to use any single set of styling directives to alter the appearance of many elements. Helpfully, such a capability would provide a way around the previously unavoidable problem of unrefactorable, duplicated "inline" / hardcoded styles.
The code below illustrates this last point, by showing old fashioned HTML markup on the left, and the CSS equivalent on the right.
|Archaic presentational markup||Modern markup Modern CSS|
Examining the nature of CSS specificity
Explaining the concept of specificity
Having observed a basic example of CSS and HTML working together, it should be clear, even to the untrained eye, that to apply styling to a document with CSS is to undertake the following process:
- Create a body of HTML, that does not have any presentational code.
- Create a block, or several blocks of style declarations, that describe colour, font size, border, and so on.
- Tie these declaration blocks to elements by means of wrapping each with a pattern-matching string of characters known as a selector.
Herein we can see the simplicity of CSS as a concept. However, a slightly more complex intricacy of the language emerges when the CSS newcomer asks the following question: "What happens if more than one CSS rule target the same property (such as
font-size) of the same element? Which value is respected?"
As mentioned above, the CSS specification defines a declaration precedence mechanism. This mechanism works closely alongside (and as we shall see is, in fact, inseparable from) the language's declaration scoping mechanism. CSS aficionados will know the declaration precedence mechanism by the term "specificity".
The concept of specificity strongly influences long-established, conventional CSS architectural models and best practices. In more recent times, qualms regarding specificity's nature have also given rise to less conventional architectural philosophies. It is therefore crucial that this aspect of the specification is discussed in detail, if we are to understand CSS organisation methodologies.
The nature of CSS's declaration precedence / specificity mechanism can be explained with the following set of premises:
- All CSS rules have a selector component. All selectors - the strings of characters that match declarations to elements - have a pattern. For example,
.banner.banner-alert pis a compound selector that is made up of three smaller selectors, and it has a pattern of two class selectors and one element selector. A numerical value - the specificity value - can be derived from the selector pattern, and this is exactly what a browser does for each selector when it parses the stylesheet. The algorithm is simple enough that a developer may mentally deduce the specificity of most rules without too much difficulty. However, the longer the rule, the harder this task becomes. This is why it is best to avoid the creation of excessively long selectors.
- When the browser parses the stylesheet, each rule is evaluated, and the specificity value of the rule's selector is always passed on to each declaration within the rule.
- When two or more declarations converge upon the same element, and try to alter the appearance of the same property, the value of the declaration with the highest specificity value is the value that is respected. If both declarations have the same specificity value, the declaration that appears last in the source code is respected.
For anyone unfamiliar with the CSS selector specificity algorithm, a good explanation can be found here, but it will be described in passing as we clarify this concept of declaration precedence.
Consider the following CSS code.Demonstration of increasing specificity values
Within this code, relatively speaking, the specificity value of the declarations
font-size: 12px and
colour: yellow are low (0-0-1), the specificity value for
color: orange is medium (0-1-1), and the specificity value for
color: red is high (0-2-1). Therefore, if a paragraph tag is nested within an element with the classes
banner-inverse, it will be successfully targeted by all of the shown rules, but it will be styled red, because the
color: red declaration is associated with the selector that has the highest specificity value.
The principle of apt declaration precedence
When thinking about specificity, it's important to realise that a declarations specificity value, or it's resistance to being overridden, is, by definition, inversely proportionate to it's scope. Scope can generally be thought of the likelihood that it will even match an element on the page in the first instance.
|Rule||Likelihood of matching a DOM element||Specificity value|
This fact makes the scoping mechanism wonderfully elegant - as long as selectors are written to target elements with just enough specificity, they will always be appropriately difficult to override. I will refer to this as the principle of apt declaration precedence.
Contempt for the specificity mechanism
Despite it's elegance, the mechanism of specificity is still known to cause problems in large codebases, and is berated by some developers. Situations do sometimes arise, whereby declarations do not have the desired precedence, and rules do not collate as the developers intended. The popularity of CSS complexity management systems such as BEM, which advocates a "one element to one selector" approach - to effectively shun the specificity mechanism entirely, inasmuch as is possible - is a compelling testament to the struggles with declaration precedence that some developers have experienced. But who, or what, is to blame for these struggles? Is the CSS language inherently unscalable, and is the enforcing of a flat selector model really justified? Or do such problems simply arise from developers not using the language as it was intended to be used?
The root cause of specificity woes
It can be demonstrated that two causative agents lie at the heart of all wayward specificity models. The first concept is partly linked to an issue with the specification, and partly to a sub-optimal use of the language. The second concept is entirely linked to a sub-optimal use of the language.
- The act of segregating the specificity model, and managing it in a single location, is not promoted.
- CSS is designed to be element-centric, not selector-centric.
The segregation of a specificity model, and managing it in a single location, is not promoted
As explained and demonstrated above, a selector expresses not only the scope of the enwreathed declarations, but also the precedence of the declarations, in the form of a specificity value. As a developer very consciously and explicitly targets a set of elements by typing out a selector, he or she also - more implicitly (and typically, less consciously) defines a precedence for each declaration. It can actually be said quite objectively that the assignment of a specificity value is done less consciously, as savants aside, most developers will not be able to work on a codebase containing several thousand selectors and remain acutely aware of how each one's scope and specificity collates to affect the element that they are writing styles for. The almost automatic creation and management of declaration precedence that a rendering engine will carry out under the tutelage of the CSS specification is an amenable convenience, and historically a reliable one, but the darker side of this is that it encourages less experienced developers to pay little attention to the specificity model - which can be particularly problematic when it is considered that automatic specificity management only operates well in the context of a selector-centric model. (This is explained in more detail in the section directly below.)
An implicit specificity model that is bound to selectors is also a problem even for seasoned CSS developers, when the typical usage pattern of CSS selectors is considered. In the vast majority of CSS codebases, selectors of varying kinds are scattered throughout, and more often than not, recombined and duplicated in strange fashions too. Significantly, even selector parts that are semantically linked and commonly combined can be found in disparate locations. This means that the typical specificity model is not only implicit, but also inconveniently strewn among dozens of files.
As a result, specificity issues can grow without a developers awareness, and when these issues do emerge, they can be fairly awkward to fix.
This situation is something that partly comes about from the nature of the CSS specification, or at least, from the vanilla code architecture framework of "spread selector code around everywhere" that it implicitly promotes.
However, despite this, a clear, explicit specificity model can actually be cultivated by the developer - to a reasonably large extent. By employing selector organisation techniques, such as those listed below, specificity crunches can be largely avoided.
- Keep compound selectors as short as possible, as per the principle of apt declaration precedence.
- Use a "one element to one selector" approach, as per BEM.
- Use a web component architectural structure, and namespace the majority of styles under each component.
- Use a CSS preprocessing technology to keep all selectors in one place, where they can be easily tracked.
CSS is designed to be element-centric, not selector-centric
The second cause of specificity issues stems wholly from a trend in the development world where developers are using a less CSS-specification-compatible focal-entity.
The CSS specification was developed in the context of a world in which HTML existed and was prevalent. It is very much the case that CSS was designed to style HTML elements. It is not the case that HTML was created as a candidate for the application of a preexisting CSS specification. The idea that CSS would hook into established HTML conventions, and not vice-versa, permeates all aspects of the CSS language, including the selector syntax, available properties, and not least, the mechanism for inferring declaration precedence / specificity.
This is precisely why the more recently vaunted paradigm of selector-centric CSS gives rise to specificity issues, whereas element-centric CSS does not. In turn, this is why specificity issues are more fundamentally caused by the way that CSS is presently used, than the specification itself.
In the example below, an element-centric approach is demonstrated. A collated style for form headings is defined using element selectors in a composite fashion (
form h1). A conservative description of a nested document structure indicates an override that should occur when one element is found within another, and establishes a greater specificity in the CSS compiler for it's sole declaration of
color: #333. The precedence of the overriding colour rule is proportionate to the scope of the rule. The principle of apt declaration precedence applies, and the whole assemblage is congruent with the developer's intentions: To override the colour of the less specific
h1 rule when an
h1 is found inside a
However, in the next example, a selector-centric model causes the two rules to have the same specificity, which causes the principle of apt declaration precedence to evaporate. Despite the developers intention for one rule to override the other, the colour declarations have the same level of precedence.Selector-centric approach
To deduce which colour value should be respected, the order of the declarations in the source code must now be relied upon. Order-based override architecture is not ideal, as it is more fragile, and more susceptible to irreconcilable, circular load order dependency problems.
Another drawback of this approach is that the HTML must now contain markup, in the form of extraneous classes, which link structure to style. The markup should ideally be agnostic to the way that it is styled - that way, it is fully independant, and does not permit that any part of itself is redundant - should CSS not honour it's requests. The classes can also be described as semi-presentational markup. As we have demonstrated, presentational markup should be avoided as it is cumbersome, scales badly, and is awkward to refactor.
Notably, it has also become less clear which rule is the base rule, and which is the extending rule. In an element-centric model, it is clear that
h1 is the base rule, and
form h1 is the extending rule. Within the context of a sea of rules, as would be found in the wild, when only reviewing the CSS, it can also become entirely unclear that one rule is even intended to override another.
The above example of selector-centric CSS is slightly unfair, as the code contains what might be considered a slight sophomoric error. Some of these issues could be resolved, particularly the issue relating to an inappropriate specificity value, by writing the selector like this, which is more commonly seen:
Now it is clear that one of the rules extends the other (although which one extends which is still slightly ambiguous), and the rule's scope and specificity is more appropriate.
However, in establishing a specificity model that is not flat, part of the advantage of using class selectors - the avoidance of specificity problems - is removed. When taken to further extremes, we can see how selector-centric model, particularly when mixed with an element-centric model causes specificity headaches:Selector-centric markup Selector-centric styles
Furthermore, the presence of all the user-defined identifiers (i.e. class attributes) mandates that the upholding of a extraneous user-created DOM abstraction layer is still required. This is something to be contemptuous of, as it adds extra cerebral overhead to all HTML editing tasks, introduces complexity, and does not really add anything useful.
Summary of specificity
Specificity is a good, useful mechanism, if care is taken to keep selectors organised, and to avoid selector-centric rule-creation patterns.
Outside the context of rigorously formal CSS complexity management systems, the use of DOM abstractions and selector-centric rules should be avoided, as they tend to decimate the style declaration intersection model, and introduce complexity without introducing benefit. If declaration blocks are required to affect multiple elements, instead of attaching multiple selector-centric classes to the elements in question, it is far better to tie the declaration block to the elements by means of several comma-separated selectors. If coded organisation conventions dictate that the scope of the stylesheet is not wide enough to permit the use of this syntax, then CSS preprocessing mixins should be considered.
The use of custom identifiers in selector patterns (classes, data attributes) is necessary, useful, and agreeable, but only when the act of adding these identifiers to various elements introduces a degree of semantic meaning to them, and not when they are just hooks for arbitrary aesthetic persuasions.
Irrespective of CSS management strategy, a good CSS developer will always create CSS declarations that have a specificity - an override resistance - that is no stronger than sufficient. Furthermore, a good CSS developer will always try to keep their selector code well organised, and devoid of duplicated patterns, inasmuch as possible.
The root causes of other CSS code organisation issues
Thus far, we have established that:
- CSS is a declarative structure, designed to give developers full control over every aspect of a document's appearance.
- CSS is designed in a way that encourages the segregation of presentational code from structural markup, and it provides a mechanism for mapping styles to elements through expressive element matching patterns, known as selectors.
- CSS provides a declaration precedence mechanism, known by the term "specificity", that is companion to the declaration scope mechanism, which allows developers to describe the collation of rules, enabling economical use of declaration blocks.
- Specificity is a good, useful mechanism, if care is taken to keep selectors organised, and to avoid selector-centric rule-creation patterns.
We are now in a position to consider some of the other hallmarks of the CSS language that have a tendency to cause problems in larger projects. We will begin this examination with the presentation of a simple block of CSS code. Consider the snippet below.
In this neat little figure of 5 lines, we have expressed nearly everything about the way that CSS is used to style webpages. The example does omit several of the languages burls and flourishes, such as combinators, pseudo-selectors, pseudo-classes, expressions, animations, and so on (all of which are important for a stylesheet author to understand) but the CSS rule, which comprises of a declaration block and a selector, is, by far, the most fundamental figure. It may not look like much, but it is also, by far, the most feared figure. All CSS organisation methodologies have at least one thing in common - they all aspire to tame it. So what makes it so scary?
It's the prospect of managing 10,000 of them.
Managing CSS rules in great multiplicity can require a carefully planned architecture for two main reasons:
- Everything is global
- Selector-centric models are used, and sometimes, combined, with element-centric models
As already touched on, the latter use trends can cause specicifity problems, poor rule intersection and false architectural dichotomy.
Everything is global
Whenever CSS rules such as the one above are fed into the browser - whether by means of a link tag or a style tag - they invariably just get tacked onto the rendering engine's master rule list for that document. This entity is nothing more than a single, undivided, (but ordered) list of "selector + declaration block" rules. When the browser paints the page, selector specificity does come into play, but in the context of its basic nature as a simple, flat list, it doesn't have any concept of sections, divisions, nestings or scopes, and can therefore be easily polluted.
A good CSS developer will be fearful of creating naming collisions, and of creating styles that are picked up in places that they should not be. For this reason, a CSS developer should write rules based around selectors that are appropriately specific.
Selector-centric models are used, and sometimes, combined, with element-centric models
As demonstrated above, CSS works best with an element-centric model, where CSS selectors target elements, and not vice-versa. Using a selector-centric model can bring a plethora of maladies, but using a mixture of both types can give rise to a separate set of issues such as particularly poor rule intersection, and false architectural dichotomies.
Consider the code below. It shows some markup within the context of selector-centric button styles, and element-centric component styles. The component styles seek to intersect with, and partially override the button styles by describing various rules that have a greater specificity value.Actions module markup Global button styles Actions module styles
The button stylesheet describes the appearance of a general purpose button, to be used throughout the website. There is nothing interesting about this rule in and of itself. However, the designer has specified that the appearance of the buttons within the actions module should diverge slightly from the standard button aesthetics. Therefore, the developer has created a rule in the actions module stylesheet that selectively overrides the global button styles. This is good code in a sense, because it makes use of the CSS specificity mechanism to avoid repeated declarations.
However, this code is also a little confounding, because the "base class" (
.button) is being extended by a "subclass" (
.actions-module .button) that exists in an entirely separate area. This area is separate conceptually, but most likely, also categorically, if the project CSS has been sensibly broken up into files and directories. The
.button class is a selector-centric, highly reusable class, so it will exist in a general purpose area within the filesystem, whereas the
.actions-module is a web component class, so it will exist within a components area. If the HTML is taken away, nothing except the class selector identifiers (which are tentative links at best) will logically connect them. Extending one class with a subclass that is so distant makes for some very woolly, unsemantic dependency structures.
The dissonance stemming from the mixture of selector-centric and element-centric rules intensifies when the definition for the variant of the login button is considered (
.button.login-button). Was the decision to conceptualise a button within the actions module as an aesthetically divergent actions module login button correct, or, considering that the button only diverges by means of a different font size, would it be more elegant to define a general purpose, reusable global button variant
.button.button-large-text within the buttons stylesheet?
This architectural uncertainty is not particularly troubling when considering a clean, hypothetical fistful of code, but when the codebase contains dozens of components, and dozens of shareable utility classes, reasoning about where each declaration comes from becomes more frustrating. Trying to decide where new declarations should be appended can be even more troubling.
In using a mixture of focal entity models, the developer has essentially defined the appearance of a class of element in two different places in the CSS, and has advocated the continued development of the software as two halves of separate systems that are only incidentally linked by a third location - the HTML.
The code above would be much more nicely written as follows. Note the leaner markup, more concise styles, and that the replacement of the login button's
Using a mixture of focal entity methods can also cause poor rule intersection. Sometimes, more overrides exist than new declarations. Defining pairs of declaration blocks that do not tessellate well creates a lot of code bloat and redundancy, which is bad for readability, comprehensibility, and file size. Consider the example below, which is a brief example of a poor rule intersection policy.Menu module markup Global button styles Actions module styles
Here, the developer has written HTML that defines a menu module. Out of habit, they have given each
button element a class of
button. The developer has reasoned that, semantically, each
button element is also a
.button element. (Incidentally, this is another example of how using a selector-centric focal entity model creates unnecessary complexity, in that developers commit themselves to maintain a (frequently) rather irrelevant DOM abstraction).
Note, however, that the developer has also decided that buttons within the menu should be a different size and colour. This means that nearly all of the button declarations are overwritten by the menu module's button declarations, which is quite absurd. From a styling point of view, the button elements within the menu are elements in their own right more than they are instances of the common button class.
Yet more problems arise from mixing focal element models. Rules that are designed to be highly reusable, such as
button sometimes need to override overrides that exist within components, which typically have longer, more specific selectors. In order to overcome this, and have its own styles take precedent, the reusable rule needs to copy, and then lengthen the component's selector. An example of this problem is shown below. Here, a developer has written a selector within a general button stylesheet, that references selectors that originate from a component stylesheet, to enforce an active button colour style.
This pattern is undesirable, as it causes the file for the reusable rule to contain references to exclusive components. This creates a tight and questionable coupling, which can be quite easily corrupted through means of redundancy in the event that the latter components are removed.
Any time a developer is working with CSS and HTML to create a styled webpage, he or she will be spinning a web of presentational code around one of these conceptual wheels - element-centric, or selector-centric - whether they are cognisant of the fact or not. The specification begets a language that will let the developer choose a focal entity model, and then skip backwards and forwards between each one as their mood takes them.
However, as demonstrated, the language is intended to be used, and works best with, an element-centric model. Using a selector-centric model, or building a CSS solution around a rapidly changing rule scoping model without great care can result in the development of some arbitrarily divided, poorly intersecting style declarations, and verbose, duplicated selectors in a larger codebase.
A good CSS developer is wary of using a selector-centric model, will always be particularly cautious of mixing rule scoping models, and will be fearful of creating poorly organised, poorly intersecting rules.
This concludes the tour of CSS and the discussion of the hazards that can arise from misunderstanding, or acting contrary to, it's essential nature. We are now in a good position to establish clear cautionary maxims.
Defining an imprudent use of CSS
Following the discussion above, the subsequent list of rules can be asserted.
- Do not mix presentational code with structural markup - do not write presentational HTML.
- Do not write selectors that are not specific enough to avoid collisions.
- Do not write selectors that are excessively specific, and therefore inappropriately resistant to overrides.
- Do not carelessly conflate element-centric and selector-centric style scoping models: Do not create sets of rules that are uncleanly divided; that intersect poorly.
- Do not allow styles for any given component or module to appear within or among a collection of styles that are for another component or module.
There are several more important rules that have been touched upon, but not explained in sufficient detail.
- Do not write duplicate code.
This rule is an old adage in the programming world, and adhering to it will grant substantial protection from a variety of technical maladies. In CSS, keeping repeated code to a minimum will bring the following benefits, specifically:
- Reducing the number of rules that contain similar declaration blocks will make large, sweeping style adjustments quicker, and will make it easier to deduce which declarations are being used where.
- Reducing the amount of markup that exists only to tie elements to styles will make large, sweeping style adjustments quicker, and will keep the HTML easy to read and adjust.
- Any initiative to keep code DRY will bring a reduction in filesize.
- When using selector hooks in markup, do not use ambiguous or misleading identifiers. When creating a reusable class, ensure that it's purpose is clearly defined.
Ambiguous identifiers within selector hooks (the class attribute, data attributes, and so on) can be disastrous, particularly in the context of highly reusable utility classes - which, as we have discussed, should be avoided as selector-centric rules. If a utility rule's purpose is poorly defined within it's selector, it may be inappropriately leveraged by uninformed developers. This can eventually necessitate an arduous refactor to fix problems that may have slowly grown through it's misuse.
Consider that a developer, Alice, creates a
.wrapping-div rule, that is intended to apply some block styling to a container element.
Consider that Alice applies this class to a lot of elements throughout the view code, such as:
Now consider that her colleague, Bob, starts plugging his sidebar modules into this rule, to give them a nice bit of emphasis among the rest of the sidebar, which has a white background:
Now consider that Alice decides that the page needs to be centred. She amends the
.wrapping-div element to contain margin declarations. She also decides that she doesn't want it to have a background colour any more, so she removes this.
This breaks all of Bob's sidebar modules, as they ought not be centred. This problem has arisen as a direct result of the
.wrapping-div rule being named unclearly. (And from using a selector-centric model). Bob now must do one of several undesirable things:
- Create a supplementary rule that will augment the now unsuitable
.wrapping-divdeclarations, reintroducing the desired background and margin values, but which is scoped to only affect the elements within the sidebar. This is undesirable, as its bad practice to needlessly override overrides, and it will amplify the confusion that has arisen from the ambiguity of purpose. In reality, Bob's sidebar divs are more an entity unto themselves than they are a subtype of Alice's page wrapping divs.
- Create a new rule within the CSS files for sidebar divs (i.e.
.sidebar-module-wrap), which houses all of the desired declarations, and then comb through the markup, replacing all references to the
.wrapping-divwith the new class. This is undesirable because it will be fiddly.
For these reasons, it is important that arbitrary identifiers that appear within rules are descriptive. However, they must not be descriptive in a misleading way. In particular, it is considered bad practice to use identifiers that allude to declarations. Consider that a developer creates the following, reusable rule:
Now imagine that the designer has decreed that the headings should now be orange, and not yellow. The developer simply changes the
color: yellow; declaration within the rule to
color: orange;. However, the name is now misleading, and many manual edits to the HTML will be required to fix this.
- Do not use a selector-centric focal entity model.
The dangers of selector-centric models are demonstrated above in passing, and discussed above in the context of specificity. However, they have not yet been thoroughly discussed in the context of presentational markup, and the principle of "don't repeat yourself". Consider the following example, which shows selector-centric markup at its most extreme. This is a highly characteristic of the atomic CSS methodology.
Proponents of Atomic CSS might argue that this is not presentational markup, as it does not contain declarations. However, each individual class here represents an individual declaration, which certainly makes it equivalent, once a thin layer of indirection is factored out. This type of markup is condemnable from a code style perspective, as:
- The concerns of markup and style are mixed in together. This means that whenever a developer digs into the markup to impart change to the structural markup or the presentation, half of the code will always be an obstacle.
- A class attribute, and the ecosystem that it fits into, are entities that are optimised to accept taxonomical assignments, not a list of styling declarations.
- It makes the process of imparting broad changes to a website's aesthetics unnecessarily difficult. On the majority of web projects, the designer (or the developer, acting as a designer), will, at various points during development, decide to impart small or large changes in aesthetic direction. Assuming a codebase that contains markup for several hundred buttons, and a coding style like the one shown above, the task of augmenting all buttons to use a tertiary background colour, and a medium font size, will be a relatively large and awkward task. This will particularly be the case if much of the HTML is housed within some kind of CMS or distributed data structure. If, however, all markup for buttons just consisted of a
buttontag with a class of
button, which was associated with a full gamut of declarations housed inside a stylesheet, only two lines of code within the same place within a single file would need to change.
The prohibition of selector-centric rules may seem overly prescriptive, but having demonstrated the pervasiveness of the issues that they can cause, particularly in the context of a large project, it is justified. Certainly, they should at least be avoided. The rule is also a logical extension of many of the other rules that have been established:
- Do not write duplicate code.
- Do not mix presentational code with structural markup - do not write presentational HTML.
- Do not carelessly conflate element-centric and selector-centric style scoping models: Do not create sets of rules that are uncleanly divided, or that intersect poorly.
The entire list of things to avoid when writing CSS for a large project is as follows:
- Do not mix presentational code with structural markup - do not write presentational HTML.
- Do not write selectors that are not specific enough to avoid collisions.
- Do not write selectors that are excessively specific, and therefore inappropriately resistant to overrides.
- Do not write duplicate code.
- Do not create sets of rules that are uncleanly divided or that intersect poorly.
- Do not allow styles for any given component or module to appear within or among a collection of styles that are for another component or module.
- When using injectable DOM mutations (aka selector hooks) in markup, do not use ambiguous or misleading identifiers. When creating a reusable class, ensure that it's purpose is clearly defined.
- Avoid the use of a selector-centric focal entity model.
Defining an appropriate use of CSS
Having established things to avoid, it is now possible to asset rules that should be adhered to, while striving to create understandable, scalable CSS.
- Presentational code should be entirely separate from structural markup. This includes keeping injectable DOM mutations (extra classes, data attributes) to a minimum.
- When used, injectable DOM mutators should consist of identifiers that are agnostic to style declaration implementation, descriptive and concise.
- Selectors should be no more or less specific than sufficiently specific.
- Selectors within CSS files should be well organised. They should not be repeated, if possible.
- An element-centric focal entity model should be used. A selector-centric model should be avoided.
- Rules should be separated by purpose. Global rules, component rules, and selector-centric rules should lie divided in the codebase. Component stylesheets should only contain rules for one component.
- A robust set of global styles and resets should underlie all project styles.
Examining CSS organisation systems
Regarding Atomic CSS
Atomic CSS is a selector-centric CSS paradigm that promotes the idea of creating a large number of very small, highly-reusable, "atomic" rules. Rules are mixed and matched to create the desired composite appearance for each individual HTML element, on an ad-libitum basis.
The technique is easy to grasp with an example.
Proponents of the system frequently cite the following advantages, but it must be said that most of these supposed advantages are borne from rather dubious reasoning.
- "Development is made easier by eliminating the need to write CSS."
- "The lack of shared, complex, compound rules makes refactoring styles simple, and there is no risk of inadvertently changing the appearance of elements that should remain unchanged."
- "As Atomic CSS rules only contain one declaration, compound rules do not exist, and therefore, user-defined classes of element do not exist. Therefore, no effort is expended trying to concoct meaningful names for them."
- "As Atomic CSS rules only contain one declaration, specificity is never a problem. Declarations can never conflict, and the specificity value is never called upon to settle disputes."
The first claim is highly dubious, as in reality, a developer using the Atomic CSS methodology is still writing CSS. The only real difference between a developer adhering to a conventional CSS methodology and a developer adhering to the Atomic CSS methodology is that the latter developer is writing CSS within markup - not within a stylesheet. This is not a good thing. The golden rule about not mixing presentational code with markup is broken, and the codebase becomes harder to read, and harder to refactor as a result.
One particularly relevant disadvantage is that it is time consuming and awkward to impart sweeping presentational adjustments upon code which contains a large amount of presentational class selectors. This has been demonstrated in the previous example involving the
.wrapping-div class selector. The larger the project becomes, the more this becomes an issue. Markup held within a database, or another kind of less accessible location, will further complicate the matter. With this in mind, it is quite undeniable that Atomic CSS does not inherently scale well.
It is true that developers are completely safeguarded against inadvertently changing the appearance of collateral elements during aesthetic refactors. Or at least, this is true when unintentional changes occur as a side effect of a thoughtless edit of a shared declaration block. However, if a small amount of care has been taken in establishing meaningful rule organisation, this simply will not be a significant risk in the first instance. When refactoring Atomic CSS markup however, editing hundreds of pages of HTML manually to pull out 3 classes and supplant 5 more in dozens of locations (or indeed, doing this with automated tools) also introduces risks, with a slightly different vector, centred around inadvertent code modification.
To achieve a similar effect in the context the Atomic CSS methodology, one would have to do something like the following. (Note that for brevity, the CSS counterpart has been omitted.)
The last point is also completely valid. Atomic CSS prevents conflicting declarations, as elements are only targeted with rules that contain single declarations. However, again, this is more a testament to the one-bit, ineffectual nature of the approach than it is a testament to the methodology's strength, and this point as a whole is somehow profoundly indicative of the methodology's character.
Atomic CSS is light, convenient, and can be a useful angle when the goal is to quickly create a throwaway prototype, or to experiment with an architectural curio. But Atomic CSS is somehow like a child's version of an adult thing. It is rounded-off, excessively governed, diluted. Used in the context of a large commercial project, or some other complex purpose, one cannot help but envisage that (without some sophisticated tooling and contrived intermediary processes) it would be completely inappropriate. It is a knife that has been blunted, a motorbike that has been limited to a slow speed, a guitar with one string. It is fun and convenient for small things, but when the purpose grows, it becomes a liability.
BEM is an element-centric CSS and HTML organisation system, created by developers working at the Russian multinational corporation, Yandex. It is was borne out of the desire to simplify mass collaboration on large, complex user interfaces, and it reached maturity some time around 2010. It is characterised by the following directives:
- All CSS selectors must target elements by means of non-compound, surrogate identifiers. These are typically classes. The identifiers used within selectors must follow a pattern of block, element, modifier.
- The convention of namespaced, surrogate identifiers establishes a fairly consistent "1 rule to 1 element" norm and simplifies the specificity model, making it relatively flat.
These basic BEM principles are demonstrated in the example below, which depicts two BEM components. Note that although the HTML is shown here as a single chunk of code, in some implementations, the component directory may neatly house it's own HTML.Composite markup Header styles Main menu styles
Observing this code, three things should be quite clear. Firstly, that there is a reduced chance of specificity wars - thanks to the 1 selector to 1 element trend. Secondly, that there is a reduced susceptibility to muddy rule intersections, thanks to the element-centric approach. And thirdly, that the price paid for these advantages is rather verbose and hard to read markup. This is the essence of BEM.
Therefore, the BEM system is a double edged sword. Just like Atomic CSS, it provides advantages and fosteres disadvantages. However, unlike Atomic CSS, the disadvantages do not dramatically outweigh the advantages. The key advantages are as follows:
- Prohibition of selector-centric rules: In light of the above directive, it is not permissible to create CSS rules that can be shared among components as "style mixins". This protects against inadvertent changes to third party elements in a way that, again, does not necessitate bastardised markup. Once again, however, this is an advantage that can be easily achieved simply by putting restrictions on the use of element-centric style rules, and is not unique to BEM.
- Self documentation: The CSS specification does not impose or recommend any pattern or method when it comes to naming identifiers. Every developer will have their own way of naming elements. Therefore, the pattern of "block element modifier" brings consistency to projects that are worked on by many developers. Additionally, the scheme makes it more likely that a developer receiving code from another will understand the purpose of each surrogate selector.
When considering these observations, the popularity of BEM among large enterprises becomes fairly obvious. It's self documenting nature and component level sandboxing make it suitable for workflows whereby very large teams work on different areas of the interface simultaneously, and then hand the code over to other developers at a later date. Considering the global nature of CSS rules, it is also easy to see how the namespacing of selectors, the relative flattening of the specificity model and the prohibition of highly reusable, selector-centric rules makes it very suitable for voluminous codebases.
The system is also fairly congruent with the above list of do's and don'ts that were teased out of the discussion of the CSS specification. It does step on the toes of one or two of the guidelines, however.
Specificity problems can still arise
BEM is sometimes promoted as a system that completely prevents specificity issues arising through completely flattening the specificity model. However, despite it's convention of namespacing all elements, this is not the case. When compared with a conventional, less formal CSS architecture, the use of BEM only affords a relatively flat specificity model, and reduces the likelihood of running into specificity issues. The inability to completely remedy specificity problems is not a flaw in BEM per se, but to those who may have understood it to be bulletproof in this regard, it is something of a disappointment.
Consider the following code, which is a fragment of an earlier example:
Note how the menu item has two classes assigned to it, with the second being a modifier. This is conventional of BEM. In the case of modifier rules, BEM relaxes it's one rule to one element policy, to allow modifier declarations to be written economically, as extensions of base rules. Consider how the styles below will collate upon the HTML shown above.
Everything works well when a block or an element only has one modifier acting upon it. But consider what will happen if the menu item is affected by two modifiers. The example below shows a menu item that is clicked, while it also represents the currently viewed page.
active and the
pressed modifier rules contain a declaration for a background colour. In the absence of any rule with a higher specificity value, the background colour of the menu item will be determined by the order in which the rules appear within the stylesheet. As discussed previously, this is fragile. Increasing the specificity of one of the rules by means of a compound selector is the only way to remedy this.
At this point, the specificity model is becoming distinctly "un-flat". However, this is not a particularly offensive transgression. In this case, the irregularity of the specificity model is contained within a single component - they are not distributed among several disparate files. However, the same cannot be said for all rule intersections that are likely to occur within a BEM codebase. Consider the following code, which features a button block nested inside a login form block.
Note that the button component, described with a class of
button also bears a class of
login-form__button. This is another BEM convention, known as a mix, which is used to explicitly express that a block, while being a block in it's own right, is also an element / child of a particular surrounding block, and that this context should cause it's appearance to be altered. Consider that the following code is used to style the button. Note that it declares that the size and color of the button should change when it is within a login form.
Now consider what would happen if the button also bore the highlighted modifier.
This creates a situation in which the specificity beast will rear it's ugly heads in the shape of Cerberus. Three selectors are attempting to apply contrary style declarations to the same element. Presently, they each have the same specificity level, so declaration precedence is unstable. In setting about the task of remedying this by declaring greater specificity for one of the rules, as he or she did in the previous example, they initiate a cross-codebase specificity war. The login form stylesheet fights the button component stylesheet for control.
Some BEM developers are aware of an alternative practice, whereby mixes are shunned, and only modifiers are used to describe contextual module aesthetic variations. This can soothe the conflict by keeping specificty wars localised, as per the previous example. The code below shows a refactored version of the code above, in the form of a modifier class based approach.Button component stylesheet Button component markup
This is very advantageous, as it sandboxes the area of specificity model complexity within a single component. However, the complexity will still be something that developers have to consider when working on this single component in the future.
Again, its important to realise that these minor specificity problems are not things that BEM has introduced. They are problems that afflict most conventional CSS methodologies. What is being demonstrated, is that a BEM convention is unable to keep the specificity model entirely flat. In employing the BEM methodology, developers will still need to devote a portion of their cerebral faculties to problems with specificity.
It is not possible to completely and cleanly abstract the DOM into a BEM tree
The act of giving every document element a user-defined identifier creates something called the BEM tree. It is essentially a simplified, (relatively) flattened, family-friendly version of the DOM. Reasoning about it is easier than reasoning about the DOM and with full CSS selector syntax - as per the CSS specification - but at times, this simplicity is a complacency, and it's infidelity creates frustrating situations. Such situations can only be overcome by forsaking the BEM conventions, in favour of working with the real, more ornate counterparts. For this reason, it is not uncommon to find projects that are built around both the BEM tree and the real DOM / full CSS spec. Recidivism only typically occurs within a fairly small set of special circumstances, causing perhaps 2 - 4% of project selectors to be divergent. But this number is quite enough to describe a project as straddling two horses - which is an uncomfortable notion.
Let's consider an example.
Item 5.11.3 of the CSS level 2 specification defines a mechanism, known as dynamic pseudo classes, whereby the state of all elements is continually dynamically fed into the stylesheet compositor as the user interacts with the page. This allows the developer to issue declarations based on element state, by means of the pseudo class selector syntax.
Let's consider a second example.
A variety of pseudo elements also exist over multiple CSS specifications, and these are even more problematic for the constraints of BEM convention.
Pseudo elements cannot have classes or attributes assigned to them - such a feat is impossible in all major browsers. Therefore, such entities can never be part of the BEM tree. And yet, they are frequently used in the construction of user interfaces, and they represent (pseudo) elements, so there is an argument to be made that BEM should have a comprehension of them.
And now, for a third example.
In some situations, it is not possible to bring even fully-fledged HTML elements into the BEM tree. WYSIWYG editors are the bane of BEM conventions, as they do not (typically) decorate elements with BEM class attributes. While it is possible to use regular expressions or procedural DOM manipulations to add BEM into dynamically generated markup dynamically, this task is often sufficiently awkward and contrived to warrant dismissal in favour of simply letting adherence to convention suffer.
The authoring of global styles is also a grey area when implementing BEM conventions. Some purists argue that styles for any given element should only come from one place - the component stylesheet. However, manually typing a common declaration, such as font-size, font-family or colour, for every textual element in every component is tedious. For similar reasons, reset stylesheets are a controversial matter.
Maintaining the BEM tree is arduous
Earlier in this article, in the context of selector-centric CSS rules, the concept of user-defined DOM abstractions was discussed as something that can be an unnecessary hindrance when it is applied gratuitously.
Classifying elements with a
button tag as buttons, by assigning them a
button class attribute, for example, serves little purpose when they may be known merely by their tag name. On the other hand, classifying
select elements as form control entities by giving them a class attribute of
BEM, to some extent, is guilty of making an excessive demand in this regard - that developers must create and maintain an auxiliary, custom, semantic name for every element on the page, even when the element serves no special purpose outside it's remit as a
form. Such a practice creates bloated code, necessitates the management of two document models, and saps time away from developers as they are forced to think up names.
Proponents of the Atomic CSS approach would certainly baulk at the latter prospect. Sometimes elements do require custom semantic designations, but equally as much, in the words of Sigmund Freud, sometimes a cigar is just a cigar.
The following should demonstrate that BEM code is considerably more awkward to read, and illustrate that it is considerably more time consuming to write. The code block immediately below shows the BEM markup that has been used to power this website's comment form user interface. The subsequent code block shows the same markup, written in a conventional style.BEM markup Non-BEM equivalent
Questionably provided advantages
BEM is generally an agreeable system, but it does have significant flaws. Those who would decry BEM in the context of a large scale, enterprise codebase are commonly met with a supercilious scorn - how can these heathens fail to realise that large projects require an explicitly managed specificity model, sandboxed components, and descriptive identifiers, and that this is what necessitates adherence to it?! However, heavily indoctrinated proponents are frequently the ones at fault. In putting the BEM system under scrutiny, and deconstructing it's effects, one realises that such vaunted advantages can be gained quite easily through other means - means which do not yeild such profound disadvantages.
So for a moment, take BEM down from it's pedestal. Examine it closely, without fear. What are the real, tangible advantages that it actually gives us? Let's list them, so that the weight of it's worth is absolutely clear.
- Modularised, web component based architecture.
- Descriptively named selectors and elements.
- A simplified specificity model.
This is what a BEM methodology really gives us. This and nothing more. It provides us with the BEM tree too, but in and of itself, an enfeebled, inaccurate, surplus DOM abstraction is a conceptual entity that is not particularly pertinent or useful when leveraging already sensibly-nuanced web specifications.
Examining each of the advantages in detail, we can see how each is not a benefit exclusive to BEM.
Modularised, web component based architecture.
Descriptively named selectors and elements.
Assuming a web component based architecture, with short, manageable stylesheets existing alongside small chunks of HTML within the project directory structure, there is little need to describe all elements and CSS rules with surrogate identifiers. Consider the non-BEM comments form markup demonstrated above - it can be argued that without surrogate identifiers, it is still quite clear - comparatively clearer, even, for the lack of clamorous, verbose class attributes - what each element's purpose is. When looking at the stylesheet exclusively, the selectors are perhaps, in a sense, slightly more ambiguous than their BEM equivalents. However, with the HTML residing at the next door down, in a short block of HTML, deducing their purpose is not at all taxing. For any developer who disagrees, the practice of putting an explanatory comment above each selector can circumvent the need to inspect the (notably short block of) HTML, while also preventing the markup from becoming unintelligible. Selector descriptors in the form of CSS comments actually have the advantage of permitting clearer explanations, as natural language is far more expressive than a block, element, modifier pattern.
A simplified specificity model.
The raised specificity values of the compound selectors in the CSS above may have some developers feeling uneasy. However, as we have demonstrated, raised specificity is not a significant problem, so long as the irregularity that it creates it is sandboxed within a single web component. It may be a storm, but it will be within a teacup. Note that the allowance of civil specificity wars is not a concession. As we have demonstrated, even employing a BEM methodology will not entirely prevent small, localised specificity wars from arising.
Proposal for a new system
Feeling inspired and frustrated by BEM conventions in equal measure, I decided that it would be worthwhile to pursue the development of an alternative (and perhaps derivative) formal approach. My observations made it quite clear to me that it would be possible to separate advantage from hindrance, and create a system that would promote a similar level of component-based organisation, without violating the list of best practices that I had assembled - and without going against the spirit of the CSS specification.
The text that follows is a delineation of the new system, which I will refer to as Segregated CSS.
The following items describe the spirit of the methodology.
- The CSS specification should be respected, and CSS code should be of a character that is congruent with the specification's nature.
- A balanced use of best practices is superior to iconoclastic stunts.
- Presentational markup is a contrivance that should be avoided. Semantic markup decorations are of a different order and are welcomed.
- CSS preprocessors are a valuable asset, and they should be exploited so that code may be more robustly organised.
- The project team is the ultimate authority on exactly which methodologies and conventions will be adopted in the project. However, any divergences from methodology, or intended adherence to additional constraints, conventions, or best practices should be agreed upon and recorded in a clear, authoritative document before development begins.
These items describe the rules that should be obeyed when writing CSS according to this methodology.
- A web component architecture must be used.
- An element-centric rule model must be used.
- Global styles and resets should be embraced as a means to economically establish an aesthetic foundation.
- The use of user-defined HTML classes and attributes should be kept to a minimum, but such entities are acceptable when they are semantic. An acceptable use includes, but is not restricted to, identifiers that describe nodes as component mount points, element modifiers, and gatherers that signify many types of element as belonging to a common group.
- CSS preprocessors must be exploited to ensure that selector patterns (and therefore, the specificity model) are kept separate from declarations. The preprocessing architecture must also afford that selector patterns are reusable.
Given the preceding discussion, all of these rules are quite self explanatory. The last directive - the most important directive - however, may require clarification.
A developer may employ any means to separate selectors from declarations, so long as the selectors are reusable, and also, in fact, separate. The following recommended technique is provided, however, as both a suggested technique, and an illustration of the intent:
To separate declarations from rules, the architect must first ensure that all web components have separate SCSS files (or equivalent) for each of the following areas of concern:
The rules file/s should contain only CSS rules. Furthermore, all of these rules should only pertain to the component in question.
The variables file should contain only variables. Furthermore, it should only contain variables that are utilised by the component in question. A variables file may be ommitted when the rules file will not benefit from variablised colours, sizes and so on.
The presence of the bindings file is the most defining feature of the Segregated CSS methodology. Like the variables file, this file should contain CSS preprocessor variables. However, it should only contain variables of a specific order: All variables within this file must be strings that represent CSS selectors. With this in mind, it could be said that the bindings file acts a bit like a pivot table, and is used to connect selectors to declarations. The code below demonstrates an exemplary bindings file.
Given the existence of a bindings file, rules files may then adopt a format of:
The rationale behind the segregation of selectors and declarations is twofold:
- As selectors are contained in one place, a very readable, concise list that is not encroached by declarations emerges. This makes it easier to perform quick manual audits of the specificity model for any given component. As specificity wars should not break out between components when using an element-centric model, the specificity model contained within the rules file is a self-contained unit, and is impervious to external attacks.
- Selectors are now represented by variables, which means that they need not be duplicated. This makes the specificity model more managable. Furthermore, this deduplication will aid in stylesheet refactors. In the event that the markup changes and the selectors subsequently need to follow suit, code edits are straightforward.
These items describe the rules that can optionally be obeyed when writing CSS according to this methodology.
- An identifier schema may be used to namespace, qualify or categorise surrogate identifiers. However, it is up to the development team to decide on the format of this schema. As an example, all component identifiers could begin with
c-, and all modifier identifiers could begin with
Demonstration: Folder structure
The following snippet describes the archetypical file structure for a Segregated CSS project.
Separate directories exist for miscellaneous CSS files and JS scripts that are not related to a component.
A components directory exists to house components, and each one should contain SCSS, JS, image, audio, and video files pertaining exclusively to that component as necessary. If possible, the HTML for the component should exist within the component directory too. No restrictions are placed on the presence or absence of a templating language or markup abstraction language.
All filenames should include the name of the component. All SCSS files that are for a particular theme, screen size or media should be named in accordance with a key-value pattern, where the key references to the classification type, and the value references the classification value. For example:
theme-raceway are all valid. The filenaming conventions are of lesser importance within the scheme of the methodology, and may be adapted by the development team if a different, but similarly well-structured scheme seems optimal.
The following set of snippets describes a web component written in accordance with Segregated CSS directives._article-variables.scss _article-bindings.scss _article-rules.scss _article-rules-size-xs.scss _article.scss
To obtain a full demonstration of a small application that is written in accordance with Segregated CSS directives, click here.
To run the demo, unzip the file, enter the directory, and run the following commands:
Comparing focal methodologies
This document will be concluded with the comparison of the four CSS organisation methodologies that have been discussed.
The following table illustrates the favourability of the four methodologies, in respect to the seven directives for prudent CSS usage that were established earlier in this document. A methodology's adherence to each directive is ranked on a scale of -1 to 1, with values of -1 appearing in red, 0 appearing in light green, and 1 appearing in a strong green.
The criteria of code readability and overall scalability were added for the reader's consideration, as they were deemed to be important considerations in the appraisal of a CSS methodology's value.
Rankings will not be justified here, as they derive from previously made arguments. However, it is worth acknowledging that the overall appearance of the table is, of course, only of relative value, as such a figure cannot encompass all relevant criteria, or rank according to the elucidated criteria, with absolute objectivity.
NB: "Ad-hoc CSS" refers to the practice of using CSS in a loose, natural manner, that does not adhere to any formalised methodology, but which may adhere to common best practices.
|Rule||Ad-hoc CSS||Atomic CSS||BEM CSS||Segregated CSS|
|Adherence to rules|
|Presentational code should be entirely separate from structural markup. This includes keeping injectable DOM mutations (extra classes, data attributes) to a minimum.||Indeterminate||No||No||Yes|
|When used, injectable DOM mutators should consist of identifiers that are agnostic to style declaration implementation, descriptive and concise.||Indeterminate||Yes||Yes||Yes|
|Selectors should be no more or less specific than sufficiently specific.||Indeterminate||Yes||Yes||Yes|
|Selectors within CSS files should be well organised. They should not be repeated, if possible.||Repeated||Yes||Repeated||Yes|
|An element-centric focal entity model should be used. A selector-centric model should be avoided.||Indeterminate||No||Yes||Yes|
|Rules should be separated by purpose. Global rules, component rules, and selector-centric rules should lie divided in the codebase. Component stylesheets should only contain rules for one component.||Indeterminate||No||Yes||Yes|
|A robust set of global styles and resets should underlie all project styles.||Conventional||No||No||Yes|
When comparing Ad-hoc CSS and Segregated CSS to Atomic CSS and BEM, an interesting trend emerges. The former two methodologies have not incurred any negative scores, while the latter two have. If the reader permits that the author constructed the rules of prudent CSS usage well and impartially, and that the author is not pursuing any conflict of interest in the form of a conclusion, (admittedly, this could be a big ask) it could be deduced that this trend emerges from the character of each pair.
Ad-hoc CSS and Segregated CSS respect the CSS specification. Atomic CSS and BEM CSS bend the specification in rather perverse ways to give the developer certain advantages. While the latter two methodologies do deliver advantages, these advantages are tied to disadvantages. To understand why this may be, consider the following premises:
- The CSS specification was built to enable developers to style HTML documents.
- The CSS specification was built very well.
- The specification begets a number of features. When all features are employed in a balanced manner, the power and convenience of the specification manifests itself in the best possible way.
- The number of features is small. If one feature of the specification is forsaken to generate a greater level of control over another feature, usage of the language becomes imbalanced. Advantages may be resultant, but they will manifest themselves with fairly equivalent disadvantages.
- The best possible way to manage complexity in CSS, to the greatest extent, with the greatest level of convenience, is not to fight the specification. The best way is to embrace it, spend time understanding it, wield it with finesse, and govern it only with gentle corrections and coercions.