There’s a chance your CSS components suck and here’s why.
Disclaimer:
This post is extremely subjective and may cause anxiety, fear, even rage. You are reading this at your own risk.
TL;DR:
When writing CSS, think in components: design them to be portable, reusable and extendible but most importantly, identify behavior that’s external to your component and componentize it as well. Thank you.
Full story:
You write CSS for a medium/large project. You’re lucky to work with a designer who’s aware of both benefits and limitations of this technology. Maybe he writes code too, that’s even better. His deliverables however are only .psds. Speaking of .psds… you just got a fresh one. It’s super awesome, as usual. It strictly follows a styleguide you and other team members managed to develop over the past few months. It makes heavy use of your extensive palette of well documented / implemented ui elements. You’re like, “Heck yeah, I’ma code this… oh wait.” What just happened? The designer indeed utilized one of your components but in a completely new context, which makes implementing it a bit tricky. Actually, quite tricky indeed. Here’s the component’s CSS structure:
.foo { /* ... */ &__bar { /* ... */ } &--baz { /* ... */ } }
You followed the BEM convention for naming your classes. You avoided element and descendant selectors to ensure portability. What could possibly go wrong? You examine the component in more depth. It was originally designed to occur in series, laid out horizontally, with a border in between:
.foo { float: left; /* ... */ &:not(:first-of-type) { padding-left: 1em; margin-left: 1em; border-left: 1px dotted #000; } }
New requirements state that it should be also capable of being put in the sidebar, where there’s only room for one instance in a row. This makes vertical borders obsolete — your awesome component now needs horizontal ones. Floats don’t play nice either. Your first take on the problem is pretty straightforward:
.sidebar { .foo { /* here's where you override stuff */ } }
Does the job indeed, but hey, that’s not BEM anymore. Also there’s a chance your company adds a junior dev to the team, who’s gonna alter the original component styles without doing sufficient testing, silently breaking all the overriden ones. Your second take is to introduce modifiers. That’s more in the spirit of BEM, right? Let’s see:
.foo { /* ... */ &--horizontal { /* ... */ } &--vertical { /* ... */ } }
Now you did the right thing, you can go grab a beer and get some rest. Actually… you can’t. Although this may look very clean, that’s not a good usecase for modifiers. They’re used to encapsulate state that’s internal to the component (--primary
, --large
, etc), not to glue it with the outer world. Also if you go crazy with modifiers, you might end up with --has-20px-margin
, --has-a-divine-touch
or worse.
Okay, so you’re now able to distinguish between what’s internal to the component and what’s not. What can you do with this knowledge? Componentize the latter! Let’s revisit the requirements: one component, two layouts. Let’s make the layout a self-contained component:
.horizontal-list { /* ... */ &__item { float: left; &:not(:first-of-type) { pading-left: 1em; margin-left: 1em; border-left: 1px dotted #000; } } } .vertical-list { /* ... */ &__item { &:not(:first-of-type) { padding-top: 1em; margin-top: 1em; border-top: 1px dotted #000; } } }
Yes, it does require some extra html:
<ul class="horizontal-list"> <li class="horizontal-list__item"> <div class="foo"> ... </div> </li> ... </ul>
But think of how much more portable this makes your component in return. Definitely worth a few extra lines — but that’s just me.