My 2¢ on scoping in CSS

Frank Lämmer
7 min readFeb 5, 2018

C in CSS stands for Cascading. While sometimes it is desired to share certain style attributes across elements, this implicit inheritance can become a nightmare. CSS beginners fight with this. And it still bothers me after years of CSS authoring. Is there a master-recipe? Let’s explore:

Problems

Style interference happens on many levels. Here are two reference cases:

1 - Authoring CSS

When managing a bigger CSS code base, you’ll most certainly come to a point where one rule overrules another.

2 - CMS plugin

You run a CMS with a cool CSS theme. Now you install a plugin which brings it’s own styles. The plugin CSS get’s overwritten by the CMS or even the other way around. The page looks broken.

Is this case considerable? Why would someone (professional) really like to have two different styles on the same page? Why for example should some form have different colors, typefaces and spacing than the rest of the page?

Adhoc solution approaches

So you are authoring CSS and experience some of your new styles are getting overwritten by some other rule. The usual quick ways to deal with this:

  • Change the order of declaration, move things down in the CSS to override values that have been set before.
  • Use higher a higher specificity, like `#foobar ul li` or `div:is(:not(.foobar))`
  • Use the `!important` rule to override stuff.

My solution approaches

Recently, while working on Teutonic CSS (yet another CSS framework), I had the idea to solve this problem once and for all. Well, turns out that it’s not that simple:

1 - A global parent selector

Good ol jQuery makes sure not so mess up name spaces with the dollar notation. Maybe I can steal the idea? This hopefully can solve interference with other styles on a HTML page. My main SCSS could look like this:

// .t is the global parent class, can be anything
// for Teutonic CSS in my case
.t {
@import "includes/_styles.scss";
@import "includes/_more-styles.scss";
}

This makes use of the cascading nature. So in order, to apply any styles, one have to add a `.t` class to an outer container like body. Looks elegant to me, but there are problems with that:

Problem A (minor): Lot’s of CSS text! The example above is SCSS where one can write nested CSS. When this get’s compiled each element will have a `.t` prefix. The built plain CSS might look something like this:

.t .topnav { … }
.t .footer { … }

That looks ugly and adds up to a lot of text. Maybe that’s just a minor problem. The file will get minified and gzipped for production, so the repeating text pattern will be reduced.

Problem B(major): Nesting still applies! Unfortunately nesting issues will appear when added on an outer container (like body) in the plugin case:

<div class="t">
<!-- The plugin will still inherit styles -->
<div class="plugin"></div>
</div>

To make the plugin not inherit any styles, different containers can be defined as siblings—on the same level—like this:

<div class="t"></div>
<div class="plugin"></div>
<div class="t"></div>

So the plugin is not sharing any styles with the other containers. But this might not be practical in the CMS plugin case, as the theme might already has an outer container and the HTML markup can’t be adjusted like that.

2 - Unique class names

Here is another scoping style that looks similar to the ones above: Just make sure that class names are unique. We can simply add a prefix on each class, like: `t-button`. With a SCSS like so:

.t {
&-button { … }
}

This actually is not a global solution with heredity, it’s a local class-by-class approach, written in a global syntax. But in fact, it works better, than the first two approaches, as one don’t have to deal with cascading issues.

See this CodePen for above solutions in actions.

Variations of the “explicit class name solution” are a common practice these days. It’s everywhere. Let’s further explore the different flavours:

Generated CSS class names

The popular CSS Modules project aims to help you with that. CSS class names can be obfuscated for production. Facebook is doing so. The Google Web Toolkit is creating CSS gibberish class-names and markup for non-humans, see:

Obscure class names in Google Drive

Looks like rocket science to me, but I guess that you’ll have to come with solutions like this on such a scale. Note that generated class names can be much shorter than human readable ones.

Is this breaking the open web?

I like to spin up the Developer Tools to see how something is done. That’s not possible with generated code like this any more. So, let’s explore further:

Unique CSS class naming conventions

Here are few methodologies not to run into inheritance conflicts:

Don’t style any HTML elements directly

A very common rule is to apply style attributes only on CSS classes, not on HTML elements themselves. I like this. It’s not bulletproof and only part of a solution. Most frameworks already include some sort of opinionated reset.css for example so isolation can not be guaranteed. Let’s see how some popular CSS frameworks approach HTML forms:

Bootstrap forms

Bootstrap uses wrapper containers for form elements as well as explicit class names on inputs and alike.

Bulma forms

Just like Bootstrap, Bulma has form group wrappers and specific class names for inputs. The form element class names are easy to memorize:

<textarea class=”textarea”></textarea>
<input type=”checkbox” class=”checkbox”>

Pure CSS forms

The outer form element has a special container class: .pure-form . The inner form elements themselves, like button and textarea don’t need any classes.

BEM, OOCSS, ACSS, SMACSS, SUITCSS and company

A popular trend is to avoid most cascading and to apply the styles at a very low and specific level. BEM (Block Modifier Element) is the poster child of a “CSS naming convention framework”. It looks like this:

<button class="btn btn--secondary"></button>

This can lead to some repetition and long class names within your HTML markup. Still, it’s semantic and works at scale.

CSS utility classes

Another modern approach are so called utility classes, the cool kids are using it. The idea is basically, to return from a semantic class naming to a more inline style kind of class naming. This is the difference:

<!-- 1. Semantic CSS class name: What is it? -->
<h1 class="section-heading">Hello</h1>
<!-- 2. Utility class CSS name: How does it look like? -->
<h1 class="b-b-xs p-b-xs">Hello<h1>

I am actually a big fan of semantic class name idea. A name like “section-heading” doesn’t say anything about the look, it’s just about what it is. So in one theme, it might have a border at the bottom, in another not. While with a utility class name it’s only about how it looks like.

Still I like utility classes more and more, as it keeps my code DRY. Separation is built-in and it makes a design system more versatile and open.

The future ahead

<style scoped> to the rescue?

I think it is not known by many. There is `:scope` in HTML:

<div>
<style scoped>p { color: red }</style>
<p>Look ma, I'm red.</p>
</div>
<p>Look ma, I'm not red</p>

That would solve some things. But it seems that it has been removed. So let’s forget about this quickly and see:

Shadow DOM ante portas!

Now, finally. Encapsulated web components are coming. The Shadow DOM has the potential to solve all above. It allows to separate CSS (markup and JS) for contents of a “HTML page” and you can even have your own custom elements, so you can even style the elements themselves. Look ma, no CSS classes required:

<!-- Silly example -->
<flag-icon country="de"></flag-icon>
<style>
flag-icon[country=de]:before { content: '🇩🇪' }
</style>

Conclusion

Starting with a CSS pre-processor, the nesting feature was so easy, convenient and attracting. So I wrote in a hardcore nesting style like this:

.container {
.wrapper {
.main {
.nav {
ul {
li {
border: 1px solid red;
}
}
}
}
}
}

Here, new tech supported my bad habits. I think that the Shadow DOM can be the master-recipe when used wisely. And I can also imagine to see more mashed designs and bloated pages, as it is now possible to mix any kind of plugins together. Let’s see.

Further readings

--

--