OOCSS, SMACSS, and the BEM naming convention have affected the way we write CSS for years. They've made it easier to work with CSS at scale. While it's possible to follow these patterns/principles in plain ol' CSS it can be ridiculously painful and considered a waste of time. Hence why people who write without preprocessors are oblivious of their importance.
The concepts of modular CSS are derived from taking classes in CSS as objects. In order words, encapsulating various aspects of our CSS code for clarity and better reusability. OOCSS introduces the concept with the media object.
Without BEM and Sass we achieve the media object this way: ###### HTML ```html ``` ###### CSS ```css .media {margin:10px;} .media, .bd {overflow:hidden;} .media .img {float:left; margin-right: 10px;} .media .img img{display:block;} .media .imgExt{float:right; margin-left: 10px;} ``` The primary problem BEM solves is nesting hell. If you've worked on enough projects in CSS you know by now to [avoid nested selectors][5] where you can. Perhaps you've heard about the [inception rule][6]. BEM-ing the media object makes it an easier code to maintain while it still serves its purpose of having a DRY CSS. This gets even better with the Sass [ampersand character][7]. ###### HTML ```html ``` ```sass .media{ margin:10px; &__description{ overflow: hidden; } &__image{ float: left; margin-right: 10px; &__avatar{ display: block; } } } ``` Notice how everything have a unique class of its own and sub-classes are only nested by the BEM block-element delimiter. This way we save a code from unwanted specificity spikes and keep it easier to maintain. We can go a step further by introducing the [CSS applications of Single Responsibility Principle][8] with BEM modifiers. Imagine this media object is a regular chat thread like the ones on hacker news. HN has flagged comments that are grayed out. To style flagged comments we will be going against SRP and DRY code by creating an entirely new media object for such cases. A better solution will be to have a modifier class that grays the content as flagged resulting in a mark-up like this: ```html ``` The improvements with BEM now encapsulates sub element classes within block classes. We may call the block class a namespace as all it's elements and modifiers are namespaced by it.
Going further with Namespaces
Harry Roberts suggests some namespaces that can be applied to achieve clarity and confidence in modifying code at any given time. To get a broader view on them you should check out the article. The namespaces he explained in more details include: o-
Object classes, c-
Component classes, u-
Utility classes, t-
Theme classes, s-
Styling classes, is-
has-
State classes, _
hack classes, js-
JavaScript hook classes, qa-
For QA testers. My regular picks from the list include c-
, u-
, is-
/has-
, js-
, and t-
.
Component classes
I have a strong preference against CSS frameworks and I believe front-end tooling should be on demand rather than preloading unneeded components that ship with frameworks. In our most recent project I had to make custom design for accordions, in-page tabs, custom scrolls, a custom listing. All these kind of components belong in the component namespace. I didn't adhere to its applications for the project because I got lazy but it's the right way to go for future projects.
Examples:
.c-accordion{}
.c-tabs{}
.c-notice{}
Utility classes
Utility classes are what I used to call helper classes. Before learning about namespacing utility classes I used to keep all of such classes in a _helpers.scss partial. It includes classes you can simply throw in to any element for rescue like:
/* Because ghosts occupy no space */
.ghost{ display: none; }
/* Invisible objects do */
.invisible{ visibility: hidden; }
.txtcenter{ text-align: center; }
.fullwidth{ width: 100%; }
Notice how the helper classes are also adhering to SRP. I must have gotten the idea of naming them helpers from using a server-side MVC like codeigniter where we had helper functions (that's in most MVC). I thought the idea of putting each of them in the u-
namespace is perfect and I since kept to that
.u{
&-ghost{ display: none; }
&-invisible{ visibility: hidden; }
&-txtcenter{ text-align: center; }
&-fullwidth{ width: 100%; }
}
Utility classes are great. However, you should be careful when you use them as they have their own caveats in responsive design. Ben Frain describes that here (also look in the comments).
State rules
SMACSS made me understand how to apply state rules and while I've seen people still use these state classes like they would their common .active
state classes, state classes shouldn't be nested in their parents. That makes this wrong:
.menu{}
.menu .active{}
.menu{}
.menu .is-active{}
applying is
prefix doesn't make it right if you're still nesting. Here's how it should be instead:
.menu{}
.is-menu-active{}
This way you can have a _states partial you import at the end of your manifest/main Sass to override the default state.
/* _states.scss (is, has) */
.is{
&-menu-active{}
&-tab-clicked{}
}
.has{
&-overlay{}
}
Having it this way will often save you from having to use !important
on state classes even as they are allowed for state rules as mentioned in the SMACSS guide. For our team we recommend chained selectors as the next go-to if the state rule doesn't override the default state and we almost never have to use !important
.
JavaScript Hooks
When elements have the js-
class they should serve only one purpose which is to hook them to a JavaScript listener. If you need to style a component that has a js-
give it another class name for the CSS. Doing anything otherwise will be going against code maintainability as a JS engineer that have gotten rid of the use of that class in the JS code may simply decide to take it off the HTML and this shouldn't affect the layout style.
Template rules
Still on our most recent application I had to create themes for the platform and with applying the t-
prefix with angularJS made a perfect combination. By having a body class t-{{themeName}}
I could easily substitute the variable themeName
for whatever theme the user chooses. When none is chosen it uses a t-default
.
Taking modular CSS to a next level
If you start applying every methods and patterns discussed then you're on the right path. A feature I still look forward to from the CSS drafts is the @module
as it extends the modularity of our code beyond everything we've covered so far. With this you can say for sure that you're writing object oriented CSS.
Modules house mixins and variables in its own scope. If you've been using variables and mixins in Sass then you know they are in a global scope which means you can have a variable $foo
but if you need another variable $foo
for a different case you'd have to come up with another name as the name already exists in the global scope. Here's an example:
@var $foo blue;
@module myWidget {
@var $foo red;
@mixin bar { prop: value; }
}
.pod{
color: $foo; /* blue */
}
.pod--red{
color: $myWidget.foo; /* red */
@mix $myWidget.bar
}
Best part is how you can use a namespace within a rule with use
statement as seen in PHP.
.pod{
@use myWidget;
color: $foo; /* red */
@mix bar;
}
Until these become valid which may be decades from now, we can still maintain a modular code with the powers Sass provide and patterns that have been recommended.