Close menu

MLC Styling

An experiment with CSS

A long time ago, a common approach to CSS was to separate concerns between stylesheets that dealt with a page’s Layout, and those that dealt with the pages content. It worked pretty well, and this organisational approach leveraged one of CSS’s namesake features; the cascade.

Today, this approach is seldom found.

More modern styling techniques and concepts such as ITCSS, BEM, SMACSS, and OOCSS focus less on the organisation of stylesheets and more on modularisation of the selectors they use, by, amongst other things, controlling another of CSS’s key, but often misunderstood features; specificity. For the current approaches to web development, such as componentisation, this move has proved powerful, is extremely useful and clearly easier and more efficient to manage than previous approaches, particularly as projects and their styling needs grow.

These modern styling methodologies, concepts and approaches have led to the identification of certain abilities of CSS to be named as anti-patterns - for example the use of ID selectors and nested selectors, shorthand rules etc.

The arguments for this are fair. They are quite observable, provable and logical. Any developer that understands CSS will agree with most of them - and yet... we’re not entirely convinced...

Having had a long and positive relationship with CSS, we were adamant that there must be an approach that can leverage the power of both specificity and the cascade, bringing back some of the older, organisational ideas, while retaining the modularisation of style rules.

In our own opinion, our goal was quite simple; to make use of CSS’s full range of features as much as possible while positively leveraging anti-patterns as best as possible.

So we experimented with a few ideas, played with a few examples and from it we found an approach we feel works quite well - so we thought we'd share the findings.

We call it MLC (pronounced Milk - because that's more fun).

To call MLC a methodology is a bit of a stretch - we like to think of it more as a styling technique that can produce lean, reliable and predictable styling. The cost is more effort, perhaps an increased number of source and/or production files, depending on how you choose implement it, and more consideration involved in writing your style declarations.

The ideas of MLC are largely organisational and should integrate well with existing naming conventions such as BEM. It recognises and in many ways mirrors some of the great ideas from other methodologies such as SMACSS or ITCSS and as such can be used alongside them, and does not intend to, or attempt to replace them.

MLC isn’t a naming convention for your classes, nor does it mandate which selectors are used, but rather, the specificity of your selectors, which style declarations are used and how they are applied (i.e. the order they are applied).

It’s an acronym for Modifiers, Layout, Content! It brings back the old technique of separating your styles by layout and content, but also determines which declarations to use in them, the order in which you include them, and the specificity you apply.

Thinking Structurally

Before we get into the details of the styling, we need to first clarify how we think about our HTML.

HTML is a markup language. With it, we can give both semantic meaning, and structural representation to the content we write.

When we look at how we usually markup content, we immediately start to see certain patterns emerge. Our markup, whether it be for content or UI, generally wraps related elements into a structural or semantic container, like a <div> or <article>. Then, in addition to using semantic tags for the content, we also tend to add further structural divisions or elements to create a complex structure, which we will style to produce the visual design we want.

Now, let's define some terminology in light of these patterns:

We'll call a collection of related markup elements, which is wrapped by some container element, a compound structure or, for short, a structure.

The element that actually wraps the markup to form a structure, we'll refer to as a root element.

Any element inside a structure, we'll simply call a child .

Keep in mind that sometimes, a child of a structure may itself be a root for another independent structure, like so:

 
<div class="foo"> <!-- root of structure 'foo' -->
  <div class="bar"> <!-- child of 'foo' - root of structure 'bar' -->
   </div>
</div>

Selectors

With MLC, the selectors you use are important only in as much as how you will combine them in order to produce the correct specificity.

Don’t think in terms of IDs, classes or elements but rather in specificity levels of the type of selector.

Class Selector Type

With MLC, you should mostly use class selector types (classes, pseudo-classes, and attribute selectors), which will increment the same level as classes in specificity, for this reason, it’s advised to use classes for base styling, as they are the second fastest selector after IDs, can be reused and are easy to type out.

We refer to any single selector consisting of one class selector type selector (class, pseudo-class or attribute selector - i.e. a specificity of 0,1,0), which applies styles as base selectors. Most of your styling should be done with base selectors, and generally, you should use classes for most if not all your base selectors.

Type Selector Type

These are elements or pseudo-elements. Before defining your base selectors, you may want to use type selectors to apply your basic style rules. This is similar to the idea of the elements layer in ITCSS.

Another one of CSS's awesome features is inheritance, and with type selectors, our intent is to provide all your content and layout the basic styles that apply to your designs, like the fonts, paddings, colours etc, in a way that means you don't need to define them in your base selectors. Type selectors have a specificty of 0,0,1 - so they are perfect for applying such styles, as your base selectors will always take prescedence over them.

Inheritance Is Your Friend

With MLC, we want to use inheritance as much as possible. It's a great feature of CSS and when you leverage it, you can style in a more succint way, and potentially write less rules.

Some properties in CSS are inherited, For example, color. The color of a property of any element will be inherited by child and descendant elements. So, if you don't specify element styles for phrasing content, your text could inherit from your base selectors. This could be particular useful in component libraries, for example.

Another way to use inheritance, is with the inherit keyword as a value for a style property, causing an element to take the computed value of it's parent for that property. This is most useful for those properties that are inherited. However, it's best avoided For those that are non-inherited.

Whether you apply MLC or not, understanding inheritance can help you to write very efficient styles, so it's worth taking the time to experiment with this awesome feature.

Leveraging An Anti-pattern

CSS shorthands can be seen as an anti-pattern. However, we think that careful use of shorthands can lead to a net benefit - as such, MLC promotes their use in a very controlled way.

When you set a shorthand property, anything you don't supply is set to initial. This can lead to unexpected results, because different user agents may use different initial values, or use something you didn't expect to be an initial value.

So the key thing with using shorthands is that ALL settable properties which you do not want to explicitly be given the value initial, must be given a value.

At the same time, you can leverage shorthands to perform reset/normalise on your elements in a more efficient way. We'll look at the details of how this works later.

One Possible Exception

There is one shorthand that should generally only be used once and in a very specific place in your stylesheets: font.

As this shorthand will reset almost every font related style, using it in base selectors will break inheritance. For this reason, you should only have this shorthand set on the html type selector. Of course, as mentioned above, if you will be setting all the properties given by the shorthand to explicit values, or want to reset all the properties it provides for your structure, then it's fine to use the shorthand.

The Parts

Modifiers

As the name suggests, these simply modify the styles of another selector. What they modify exactly depends on the the selector it is paired with. But a key feature is, on their own, they do nothing.

A modifier should usually always change specific properties and not shorthands. Shorthand properties may be used only if all properties provided by the shorthand are being set for the modification.

Modifiers aren't always classes. We want to use the whole breadth of CSS's very powerful selectors. Remember that with MLC, we control the specificty level, not the selectors themselves - and specifically, we're controlling class selector types. So your modifiers could be classes, attribute selectors, or pseudo-classes. Pseudo-classes are effectively CSS's built-in modifiers (they select elements in a particular state - sounds like a modifier to us!), they're there, so use them!

Layout

These define style rules which only deal with the layout of an element.

There are 2 important concepts to these types of rules:

1) They NEVER change a content style (a style which does not effect the layout of the element)
2) When styling a root, IF you are not making use of inheritance, they should use shorthand properties wherever possible.

It's fine to use shorthands in child elements too, but if you want to make use of inheritance, you may want to avoid them and stick to only using them in the root element.

If the child element itself is a root, use shorthands only if you want to keep it encapsulated/componentised.

By using shorthand properties, a layout rule will set any unspecified properties to their initial values. This is a good way to perform a normalise on any element you style, without actually needing normaliser rules.

Because these rules only set layout rules, you shouldn't run into specificity issues. Instead, you’ll leverage the cascade to correctly apply styles as you intend.

Mostly, only styles that do not affect layout are inherited - and in the case of nested root structures, using shorthands will mean that any you don't want inherited, won't be.

Content

These define only rules which do not have an effect on the layout of an element, such as colours, or cursors.

3 rules around content rules are:

1) They NEVER change a layout rule - a direct opposite of the layout styles
2) They should NEVER use shorthand selectors.
3) They MUST be included AFTER layout rules

Content rules must use specific properties to set only styles that manage the aesthetic elements of the structure, such as colours or font style (but not font size etc as those affect layout).

Because these style rules will not conflict with the layout ones and are not shorthands which reset unspecified rules, they can not alter the layout of the structure in any way. This makes all your content rules hot-swappable! You also must have realised by now, that the same is true for the layout! You can hot-swap a layout or content scheme independently! Try a layout with a new colour scheme - or an existing colour scheme with a different layout! That was the main motivation for separating layout and content in the years gone by.

Applying MLC

Now that we've defined the technique, let's see how it's used.

Using Base Selectors

Let's take content structure. Perhaps it's used on a homepage. This structure has an <article> container, a <h1>, a <p>, and an <a> to link to some other page.

<article>
  <h1>Widgets</h1>
  <p>
      Lorem ipsum dolor sit
      <a href="#">amet</a>
  </p>
</article>

The article tag is our root - we'll class this with promo-box. The elements it contains all belong to the same, single structure. We'll class the rest as follows:

<article class="promo-box">
  <h1 class="promo-box_title">Widgets</h1>
  <p class="promo-box_blurb">
       Lorem ipsum dolor sit 
       <a class="promo-box_link" href="#">amet</a>
  </p>
</article>

Now when we style this, we'll split it across 2 files, using the same selectors - first for layout:

/* layout */

.promo-box {
    box-sizing: border-box;
    font: 400 16px/1.2 Helvetica, Arial, sans-serif;
    margin: 0 0 20px;
    padding: 20px;
    border: 3px;
}

.promo-box_title {
    font-family: 'Courier New', Courier, monospace;
    font-size: 34px;
    margin: 0 auto 1em;
}

.promo-box_blurb {
    margin: 0 auto 1em;
}

.promo-box_link {
    box-sizing: border-box;
    padding: 0.5em 0;
    font-weight: 600;
}

*Note in the above we've used the font shorthand in the root of our structure in order to demonstrate usage, but also to create a consistent base for the fonts of this particular structure.

Second for content:

/* content */
 
.promo-box {
    border-style: solid;
    border-color: #999;
    background-color: #CDCDCD;
    font-style: italic;
}
 
.promo-box_title {
    font-style: normal;
    color: #666;
}
 
.promo-box_blurb {
    color: #333;
}
 
.promo-box_link {
    text-decoration: none;
}

While we've separated these rules in completely different files (recommended), you could put these rules in the same stylesheet file, but they must be included in order - layout first, then content.

So let's look at what's happening here:

First the layout rules are included, applying with a 0,1,0 level of specificity. Next, the content rules are applied with the same level of specificity - so no one set of rules has a higher importance over the other - instead we're using the cascade feature to apply the rules, the latter rules would be the ones to win out. In our example, when we look at our inspector, we see that the styles all apply as we intend - with no unexpected overriding, no unexpected output. How come?

This is because of the style properties we used. The layout shorthands set the values of the styles you supply, but set all unsupplied values to their initial values. Because the content rules use specific properties and not shorthands, only the supplied style is set on top of the already applied layout rules. This is truly leveraging the cascade - check the inspector for this example code and you'll see none of the styles are overridden, they are all applied, cascading one on top of the other in the order of inclusion, to produce the intended styles.

A screen capture of the styles applied for the promo box structure the using MLCA screen capture of the styles applied for the promo box title using MLC

Applying Modifiers

Now what if we want to have one use case be somehow different? Then we'd introduce a modifier, or mod for short. We like to prefix our mods with m--.

Right, so let's say one box has a mod:

<article class="promo-box m--different">
  <h1 class="promo-box_title">Widgets</h1>
  <p class="promo-box_blurb">
       Lorem ipsum dolor sit 
       <a class="promo-box_link" href="#">amet</a>
  </p>
</article>

For the modifier rules, we'll either need to add another stylesheet file (recommended) or include the rules in an existing stylesheet - while the order in which mods are included are technically not important, we always recommend including them last to ensure that any unexpected clashes gets handled by the cascade.

The .m--different class on its own would never be given any style rules. Instead it's only ever given rules when paired with another non-modifier selector - i.e. a base selector. That is to say, the selector of the element it actually modifies.

.promo-box.m--different {
   margin-top: 50px;
}

So what happens? All instances of the promo box will apply margin bottom of 20px and all other margins are set to 0 by the shorthand use of the layout style. The content rules are cascaded on, both applied with a specificity of 0,1,0.

The one use with the mod, will then set a value of 50px to the margin-top property only. This applies on top of the existing margin rules, but does so with a specificity of 0,2,0. This means the rule will always win out against the layout and content rules, regardless of where it is in the cascade. This is how MLC leverages CSS specificity to provide true override functionality, not relying only on the cascade to change the appearance.

Let's take a small tangent here to investigate this further:

What would happen if we didn't pair the mod selector with its base selector, à la BEM:

.promo-box {
  margin: 0 0 20px
}
 
.m--different {
  margin-top: 50px;
}

This still works right - but wait let's include the mod before the base selector:

.m--different {
  margin-top: 50px;
}
 
.promo-box {
  margin: 0 0 20px
}

That breaks! Why? because both selectors have the same specificity, so the determining factor of how they are applied is left only to the cascade, while with MLC, specificity is what determines the winning rules for modifiers.

If the shorthand wasn't being used in the base selector and it wasn't setting margin-top, then you wouldn't encounter the problem. But relying on those kinds of 'requisites' is a little brittle, and does not provide true override.

But what if you want to modify something else in the root structure, let's say the <p>? You would just add a mod class to that element too.

<article class="promo-box m--different">
  <h1 class="promo-box_title">Widgets</h1>
  <p class="promo-box_blurb m--different-p">
       Lorem ipsum dolor sit 
       <a class="promo-box_link" href="#">amet</a>
  </p>
</article>

And define your mod the same way, using both selectors:

.promo-box_blurb.m--different-p {
  margin-bottom: 50px;
}

The fact that our MLC mods don't do anything on their own, means there's no reason why you couldn't use the same modifier to do both modifications, or even others:

.promo-box.m--different {
   margin-top: 50px;
}

.promo-box_blurb.m--different {
  margin-top: 50px;
}

.promo-box.m--different {
   box-sizing: content-box;
   padding: 10px;
}

Realistically though, that is likely to cause confusion down the line - so we'd say always ensure your modifier names specifically describe the mod it applies.

Nested Selectors

Looking at all that in this example, wouldn't it be much easier to write our HTML so that we only need to mod the root element? Yes and you can, but your selector would be using a feature that many consider to be an anti-pattern; nested selectors.

But let's just see if we can safely leverage nested selectors:

.m--different .promo-box_blurb {}

This is generally considered an anti-pattern due to the way specificity is affected.

In approaches that try to control or lock specificity to a specific value like 0,1,0, these types of selectors do in fact create a problem, because they will be higher in specificity than the other selectors. However, in the above case, this is exactly what we want to do.

It's worth noting here, because it's often a misunderstood or forgotten point, that while BEM seeks to control specificity and keep it at 0,1,0, it does actually allow the above usage when using a 'block modifier'.

And whatever methodology you might be using, here's another point to consider: Do you ever use pseudo-class selectors like :hover, :last-child or :nth-child? Well those increment specificity at the same level as a class. So in trying to lock specificity, you are basically saying you will have to not use some of CSS’s most powerful features as a selector system. In practice, this never happens.

To clarify, we're not claiming or suggesting that BEM, or other such methodologies, mandate that these selectors not be used. We're just pointing out that in practice, trying to strictly control specificity limits things quite significantly.

Back to our attempt to apply nested selectors for our mods:

/* Specificity */

.promo-box_blurb { /* 0,1,0 */
    margin: 0 auto 1em;
}

.m--different .promo-box_blurb {  /* 0,2,0 */
   margin-top: 10px;
}

Our mod still works, and there are no unexpected side-effects for us, because of the fact that modifiers only change the one thing, never use shorthands and only apply for that selector combination.

Now to demonstate the specificty issue that this nested structure creates, we'll look at applying another modifier to the blurb only, and styling it as we would using only BEM:

/* Specificity */

.promo-box_blurb { /* 0,1,0 */
    margin: 0 auto 1em;
}

.m--different .promo-box_blurb {  /* 0,2,0 */
   margin-top: 10px;
}

.m--different-p {  /* 0,1,0 - (goes on .promo-box_blurb) */
   margin-top: 8px;
}

You can see here why this will fail. Our nested selector applies a style with a higher specificity than our BEM modifier.

Now, let's apply this with MLC:

/* Specificity */

.promo-box_blurb { /* 0,1,0 */
    margin: 0 auto 1em;
}

.m--different .promo-box_blurb {  /* 0,2,0 */
   margin-top: 10px;
}

.promo-box_blurb.m--different-p {  /* 0,2,0 */
   margin-top: 8px;
}

This time the mod will work - because the specificity is the same as the block modifier. But, we need to note, that the reason it applies is because of it's position in the cascade. Because the specificity is the same, if the mod was included in our stylesheets before the block modifier, then the blurb modifier would not apply. That's a little brittle, but definitely manageable, as this has always been the case with BEM modifiers.

Now let's consider the wider specificity problem of styling with nested selectors (i.e. not BEM block modifiers), and investigate whether MLC can actually help.

Because a modifier is never given rules on its own, it's specificity doesn't *unexpectedly* go above the root element’s.

*it can happen however, but should be extremely rare

Pure BEM example:

/* Specificity */
 
.block {} /* 0,1,0 */
.block__p-element {} /* 0,1,0 */
.block__p-element--modifier {} /* 0,1,0 */
.block p {} /* 0,1,1 - uh oh 😞 */

Using the MLC approach (with BEM naming)

/* Specificity using MLC */
 
.block {} /* 0,1,0 */
.block__p-element {} /* 0,1,0 */
.block__p-element.block__p-element--modifier {} /* 0,2,0 */
.block p {} /* 0,1,1 - modifier above still works 😉 */

Another nice way of using nested selectors is hinted at above; to perform normalising of a root structures children. To do this you only use a class selector followed by an element selector:

.foo p {}  /* 0,1,1 */

This allows you to specify base styles for child elements which you then override using the same selector and a mod - i.e. You treat the normalised selector pattern as if it were itself a base selector:

/* The normalised child of a root */
.foo p {}  /* 0,1,1 */

/* Overrides */
.foo p.m--modifier {}    /* 0,2,1 */

When using this normalising pattern, you'll have to watch out for the specificity issues mentioned above if the child elements are being styled with a base selector.

While this is convenient - the descendant selector does not have great performance so where possible, try to avoid it in preference of others like the direct child, adjacent sibling etc. This does not mean don't use it, just use it sparingly.

Details: Resetting/Normalising

We looked at CSS shorthands earlier and mentioned how they could be used to perform CSS resets/normalise. In this section, we'll look at that possibility in more detail.

There are all sorts of CSS resets. First, lets consider an example of one of the older and easier choices:

/* CSS reset */
 
* {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}
 

/* Our styles */
 
ul {
  list-style-type: disc;
  list-style-position: outside;
  margin-left: auto;
  margin-right: auto;
  padding-top: 1rem;
}
 
.foo {
  padding-top: 1rem;
  margin-bottom: 1rem;
}
 
.bar {
  box-sizing: content-box;
  margin-bottom: 2rem;
  line-height: 1.5;
}

The first selector uses the universal selector (slooooooow...) to find EVERY element and set some rules to base values - i.e. provide a consistent base style for the whole page.

Then the ul, .foo and .bar selectors reset the elements styles as specified by overriding the reset's applied rules. That's pretty inefficient - remember in CSS an non-matching selector is faster than a matched one!

More commonly used approaches however, are resets like the Meyer reset or the HTML5 reset.

Below is an example using just a portion of the Meyer reset:

/* CSS reset */
 
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}

/* Our styles */
 
ul {
  list-style-type: disc;
  list-style-position: outside;
  margin-left: auto;
  margin-right: auto;
  padding-top: 1rem;
}
 
.foo {
  padding-top: 1rem;
  margin-bottom: 1rem;
}
 
.bar {
  box-sizing: content-box;
  margin-bottom: 2rem;
  line-height: 1.5;
}

*Remember this is not the full CSS reset, it's just used to illustrate a typical reset scenario.

On one hand this reset is more efficient in that it's using element selectors, which while slow, is faster than the universal selector. On the other hand, there's more page weight because the contents of our CSS file will be greatly increased.

Important to the reset approach, is to remember that it resets everything to be the same, effectively removing (but in actuality overriding) the user agent stylesheets - that means elements like <h1> will be the same as a <p> so you're own styles for all these elements are essential if you use them.

Another thing to keep in mind, is that many of those elements that have been reset, like <code>, <pre> & <kbd> may never actually be used in your markup.

Now, what about a CSS normalize? Rather than show you another example as with the reset, it's probably best to explain the difference. Normalize.css redefines the concept of the reset from effectively removing user agent styles to making them all consistent.

For the most part, browsers are fairly consistent in their user agent styles, but there are some aspects that differ, what normalize does is provide a chunk of rules that target those differences and 'correct' them. Author styles then apply over them, which may, or may not override the normalize rules.

Now consider the following:

/* MLC Styling */
 
ul {
  list-style: disc inside;
  box-sizing: content-box;
  margin: 0 auto;
  padding: 1rem 0 0;
}
 
.foo {
  box-sizing: content-box;
  padding: 1rem 0 0;
  margin: 0 0 1rem;
}
 
.bar {
  box-sizing: content-box;
  margin: 0 0 2rem;
  line-height: 1.5;
}

With the above, our use of shorthands, and setting of all the properties they provide, means the selected elements are always normalised across all user agents, and are also the elements that are actually used in our content - so less unnused page weight added. There's less overriding because our selectors will be the only rules needed to ensure consistency across all browsers - i.e. they normalise for us, in addition to styling.

Bottom line - there's really no need to style an element you don't use. And shorthands, if used carefully, can provide us with a good solution to ensure consistency across user agents.

Details: Files

As we mentioned before, MLC means using separate stylesheets which will usually actually contain the same selectors.

If you're thinking, hmmm... that sounds kinda' stupid! Well, we're not agreeing with you, but we're also not disagreeing with you!

There's a balance to be reached with this point. While multiple files may mean increased network requests, and all the latency, DNS lookups etc that come with it, it is also an opportunity for caching strategies to optimise this and gain the benefits from splitting layout from content - namely, hot-swappable styling.

But, splitting styles into 3 separate files may not actually be needed.

The key feature being leveraged is the cascade, and this does not necessarily mean completely separate style sheets, you may want to simply style in one file, in order of layout and content:

If you are using a CSS preprocessor, this could be done for you. But keep in mind, this does mean you lose the benefit of being able to hot-swap layouts and/or content styles.

/* The following... */

/* layout.css */
.foo {
  margin: 0 20px;
}

/* content.css */
.foo {
  color: #F00;
}

/* modifiers.css */
.foo.m--different {
  color: #00F;    
}

/* Is the same as... */

/* combined.css - ordered with layout then content and mods */
.foo {
  margin: 0 20px;
  color: #F00;
}
.foo.m--different {
  color: #00F;    
}

The real question is, whether you find it too tedious to maintain these rule categories separately. Ultimately, your requirements will determine whether MLC makes sense for you, and whether you should implement it as separate files, or as single files with rules in order.

Another organisational structure could be to include modifiers with their respective category files - i.e. include layout modifiers with your layout.css file. This will work because modifiers don't rely on the cascade.

Conclusion

That's MLC in nutshell. While this article may be quite a high level overview of the concept, when we review it, we find that we have actually covered quite a lot of concepts and details. So there's a lot to consider.

It's important to remember that MLC is an experiment and should not be looked at as a fully fledged styling solution.

We put together this article to share the idea, but it's something that's still in it's infancy, we're actively working with the technique and refining it as we go.

If you like the ideas, we'd suggest applying MLC in small, isolated areas of your projects to see how it fares. And if you do decide to try out the technique, we'd be very interested in how you find it so please drop us a tweet at @beyondthesketch.

First published: 06/11/2019

Last updated: 07/07/2020