At its best, software development is an extended exercise in evolution. Our tools evolve. Our practices our evolve. Our frameworks evolve. ("Standing on the shoulders of giants"... and all that.)
But evolution isn't always a straight-line march from worse-to-better. In fact, sometimes it feels that there's a certain banality passed down from one dev to another, like a generational curse. Or some sad rite of passage. Like hazing, it's the idea that:
I dealt with this. And eventually got used to it. So you should have to experience the same sadness as well.
In the world of software development, there are probably too many sads for any one person to count. So in this post, I'm going to focus on a very specific subset of sads: the CSS Class Sads.
The Obligatory Disclaimers
If you're the type who browses through dev-blogging sites like this one, you probably already have enough experience to realize that CSS classes are a net good. I remember when CSS was introduced. (Yeah... you read that right. I'm that old.) Up to that point, it wasn't that much of an exaggeration to say that we were making websites by scribbling HTML on cave walls with charcoal.
So nothing in this article is against the overall idea of CSS. CSS is a godsend that only improves over time. But while CSS is easily defined as a net good, like any other convention, it can be abused. In some cases, it's abused to the point of spreading that sadness to any other devs who are unfortunate enough to dive into the code.
The Sadness of CSS Class Dictatorship
We've all met CSS Class Dictators. Heck, you might even be a CSS Class Dictator. (If you are, then kudos to you for joyfully spreading all that yummy sadness!) Dictators are not confined, in any way, to CSS classes, nor to CSS in general. Every aspect of tech has its share of Dictators.
Dictators have decided (for themselves, for the whole team, for the entire company - and indeed, even for the whole world) that Technology X is The One True Solution To Rule Them All (and in the darkness, bind them). If you don't slavishly share their universal devotion to Technology X, you're stooopid, and deserving of any scorn that comes your way. For Dictators, Technology X is like a hammer - a hammer that's the only tool in their tool belt - and the entire world appears to them like nothing but an endless series of nails.
From the beginning, CSS always had the option to apply styles inline (with the style
attribute), or through shared CSS classes (with the class
attribute). And from the beginning, there were people screaming to anyone who'd listen that inline styles:
Cause premature baldness.
Are responsible for world hunger.
Are an attack vector for third-world covert operatives.
Smell like feet.
This mindless devotion to CSS classes - devoid of any context in the real-life codebase - was always hard enough to manage when we were dealing with plain-ol' static vanilla HTML. But with the current generation of rich internet applications, a slavish requirement for All-CSS-Classes, All-The-Time can be a downright hindrance to efficient programming.
Consider the following examples that are very similar to things that I'm dealing with even to this day:
// Example 1
render = () => {
return (
<>
<div style={{
float: 'left',
borderTop: '1px solid green',
fontSize: '1.3em',
}}>div1</div>
<div style={{
float: 'right',
borderBottom: '1px dashed orange',
fontSize: '1.1em',
}}>div2</div>
<div style={{
clear: 'both',
borderLeft: '2px dotted red',
fontSize: '0.9em',
}}>div3</div>
</>
);
};
// Example 2
render = () => {
return (
<>
<div style={{
textAlign: 'justify',
borderTop: '1px solid green',
fontSize: '1.1em',
}}>div1</div>
<div style={{
textAlign: 'justify',
borderTop: '1px solid green',
fontSize: '1.1em',
}}>div2</div>
<div style={{
textAlign: 'justify',
borderTop: '1px solid green',
fontSize: '1.1em',
}}>div3</div>
</>
);
};
So here's the $100,000 question:
Of the two examples above, should they both be converted to use CSS classes? Should neither of them use CSS classes? Should Example 1 use CSS classes and not Example 2? Or vice-versa?
If you think that both of the examples should be using CSS classes, then rest assured that your CSS Class Dictator Starter Kit is already in the mail. It should be arriving any day now, complete with a red armband and some sturdy boots that are ideal for goose-stepping.
It doesn't take a rocket scientist to see that Example 1 consists of three entirely unique styles applied separately to each of the <div>
s. Furthermore, without seeing any context for the rest of the app, there's no particular reason to believe that these sets of style attributes would ever be used anywhere else.
Conversely, Example 2 has the exact same set of style attributes applied to all three <div>
s. If you've ever heard of DRY (go ahead... Google it... I'll wait) then it's not hard to see why that repeated set of styles should be encapsulated in its own CSS class.
(Note: Yes - I realize that, in JavaScript/React, we still don't have to put the repeated style sets into classes. We could, in theory, just encapsulate them in a shared object and pass that object into the style
for all elements that require that particular styling. But I'm not even going to dive into that debate within the scope of this article...)
You see... CSS classes are basically just a means of sharing style. In fact, in CSS, that's pretty much all that a "class" is - because CSS "classes" have absolutely nothing to do with OOP "classes". CSS "classes" don't even have anything to do with JavaScript "classes". In CSS, a "class" is pretty much just a shared bucket.
If you're not limiting your CSS classes to styles that are shared between two-or-more elements/components, then your classes are pretty much a waste of time - an unnecessary and potentially harmful abstraction.
(Note: Yes - I realize that CSS classes can have some benefits with regard to pre-compiling in the browser. In 99%+ of those cases, the "benefits" of said pre-compiling amount to micro-optimizations. If your app is bogging down because of all those unprocessed styles... you have bigger fish to fry than class-vs-inline-style debates.)
I don't know why this is so hard. But apparently... it is. Because I've run into so many places (even recently) where some CSS Class Dictator in the team/company has dictated that, "There will be NO inline styles!!!" And they somehow think that they've enforced good coding standards - when all they've done is spread the sadness.
It doesn't matter to Dictators if the CSS files are clogged with huge volumes of single-use classes that are incredibly specific to an exact element, being rendered in an exact component, in an exact module. In fact, nothing matters to them - other than the slavish devotion to the no-inline-styles rule.
The Sadness of Style Attributes Masked by CSS Classes
Dogmatically insisting that there can be no inline styles can lead to some weird side effects. These side effects aren't just a syntax choice - like arrow functions vs. old-skool function declarations. They actually undermine the original intent of the class
and style
HTML attributes.
Consider this simple example, written originally with inline styles:
render = () => {
return (
<SomeComponent style={{textAlign: 'right'}}>
Lorem ipsum - blah, blah, blah...
</SomeComponent>
);
};
Doesn't get much more basic than this. We have a block of text and we want that text to be right-aligned. So we just slap the easiest, and most targeted, solution on it that's possible: We use style
to set the component's contents to right-aligned. As simple as that might sound, it gets kinda strange when you're working with CSS Class Dictators.
Right now, I'm contracting for a public entity that uses their own "design system". There's nothing wrong with the idea of a "design system". In fact, they can be quite useful. But the system dictates that the code above should be done as follows:
render = () => {
return (
<SomeComponent classNames={'ds-u-text-align--right'}>
Lorem ipsum - blah, blah, blah...
</SomeComponent>
);
};
At first glance, you might think:
Class/style. Poe-tay-toh/poe-tah-toh. What difference does it make??
OK, fine. Then tell me what you think of these examples:
// Example 1
addTwoNumbers = (number1 = 0, number2 = 0) => number1 + number2;
const sum = addTwoNumbers(2, 2);
// Example 2
const sum = 2 + 2;
If you think that Example 1 is just fine, then you can stop reading right now. And please, no matter what you do for the rest of your life, never show yourself on any dev team of which I'm a member.
Example 1 purports to re-create, with unnecessary verbiage, something that already exists in a cleaner, more-efficient manner in core JavaScript. So with that in mind, Example 1 is empirically bad.
(Note: Unilaterally labeling almost anything in tech as a universal "good" or a universal "bad" is, admittedly, a very risky position to take. Most things in tech are "good"... in certain situations. And "bad"... in other situations. Nevertheless, I'm perfectly comfortable stating that Example 1 above is absolutely, positively bad.)
What does any of this have to do with CSS classes? Well, if you've created a CSS class called ds-u-text-align--right
, and its sole purpose is to right-align its container's text, well... that's bad as well. That CSS class is nothing more than an attempt to re-create, with unnecessary verbiage, something that already exists in a cleaner, more-efficient manner in core CSS. So with that in mind, the ds-u-text-align--right
class is empirically bad.
Re-creating style attributes as CSS classes is also harmful because the team that creates the "design system" rarely ever takes the time to convert all of the CSS style attributes into standalone classes. They only convert the CSS style attributes that they find themselves frequently using. This means that, as a user of that same design system, you have little choice but to mix-and-match items defined in the system with your own custom classes - or your own inline styles.
For example, while complying with this current design system, I'm forced to write code that looks like this:
render = () => {
return (
<>
<SomeComponent classNames={'ds-u-overflow--scroll'}>
Some content...
</SomeComponent>
<AnotherComponent style={{overflowX: 'scroll'}}>
More content...
</AnotherComponent>
</>
);
};
Notice that, in the first component, I'm using a CSS class for overflow
, as prescribed in the design system that I've been asked to comply with. But in the second component, I'm using an inline style attribute for overflowX
.
Why have I used these mismatched approaches right next to each other? Is it because I don't care about consistency in my own code?? Hardly. It's because the design system couldn't be bothered to define a standalone wrapper class specifically for overflowX
. It only defines a wrapper class for overflow
. So if you want to invoke the scroll
value - but you only want to invoke it on the X-axis?? Well... then you're on your own.
I could submit a pull request to the design system's GitHub. But if you think I can be bothered to do that - and to wait for the desired merge to occur - then you have no idea about the deadlines I'm typically working on.
I could create my own custom CSS class called ds-u-overflowX--scroll
. And that would look consistent to any dev who's casually reading through the code. But it would be horribly INconsistent under the hood. Because a logical assumption would be that anything starting with that ds-u-...
prefix would ultimately derive from the design system. But that logical assumption gets shredded if I start creating my own ds-u-...
CSS classes that live in the local source directories of my app.
The Sadness of CSS Class Names
Global, dictatorial use of CSS classes (in lieu of any inline styles) also causes naming problems. Because style elements can be assigned to nearly any element, and used in nearly any context, it can be difficult to give them meaningful names that also avoid naming conflicts.
The CSS Class Dictators have an answer for this. It's an answer that spawns its own set of headaches, but it's technically an... answer. They came up with (obtuse) naming conventions to avoid collisions.
There's no Single Rule For CSS Class Naming, but a common one you'll see all over is the BEM syntax. There's nothing particularly intuitive about that acronym, so I'll just tell you what it stands for: Block Element Modifier.
The fact that "BEM" doesn't naturally lead anyone to infer "Block Element Modifier" is no accident. That opaqueness is consistent with most apps that actually use BEM - and I've seen it in a great many apps. There are many other naming conventions that you could use for your CSS classes, but BEM seems to be particularly common.
The BEM naming rules call for CSS class names to be formatted like this:
[NAMESPACE]-[PREFIX]-[BLOCK]__[ELEMENT]--[MODIFIER]
Oh, mannnn... That's a lotta info to try to cram into a single identifier. In fact, it's so much information that it could easily lead to ridiculously-long class names.
Consider the following hypothetical eyesore:
render = () => {
return (
<ChildComponent classNames={'ourcompany-thiscomponent-childcomponent__overflow--scroll'}>
Some content...
</ChildComponent>
);
};
All of that junk piled into the class name - when you simply could have done this:
render = () => {
return (
<ChildComponent style={{overflow: 'scroll'}}>
Some content...
</ChildComponent>
);
};
Of course, I'm not the first person to point out that a given variable or naming convention might be unwieldy. CSS acolytes recognize these problems as well. So do they fix it with greater use of inline styles?? Of course not. That would be stooopid. No - they fix it with minuscule abbreviations that are far shorter - but much harder to read and understand.
Consider the example class that I showed you above from my real-life project:
ds-u-overflow--scroll
Hmm... What does that mean??
Well, I'll assume that most of us can figure out what overflow--scroll
maps to. But what about ds-u
?
- The
[NAMESPACE]
ofds
refers to "design system". - The
[PREFIX]
ofu
refers to "utility".
Could any outsider surmise those values merely by reading the code?? Of course not. There's absolutely nothing intuitive about "ds" or "u". They're so short and so vague that they could mean almost anything.
If a junior dev on my team submitted code that looked like this:
const ds = 'someValue';
const u = 'anotherValue';
I'd deny the pull request - and tell him to fix it. It's just not fair to expect other devs, encountering those variables for the first time, to have any idea what "ds" or "u" convey. But we tolerate this in BEM class names, because no one wants to see long names stuffed into every dang "class" attribute in their app.
If the users of the design system had to put designsystem-utility-
in front of a huge number of their CSS classes, they'd get annoyed. So to get around this (entirely unnecessary) problem, they create an entirely different problem by making their code obtuse and difficult to read.
The Right Tool For The Job
I often use the analogy of tools. As a programmer, I have soooo many quality tools at my disposal. Some of those tools get used every single day. Some of them get used only once every few months (or less). But nearly every tool has a purpose.
Walk into an artisan's shop and look at the array of tools on the bench or on the wall. Some of them are rarely used. Some of them are sturdier than others. They can be cheap or expensive. But artisans don't point to one tool and call it "good" or "right", and then point to another tool and call it "bad" or "wrong". They understand that for every job, you want the right tool for that job.
Unfortunately, many programmers don't (or refuse to) grasp this concept. They get all fired up about CSS classes. Then they decide that CSS classes are The Only True & Acceptable Solution. If they get enough clout in their team/company, they even create arbitrary rules like: "NO inline styles!" Then they nearly slip a disc as they jump through all sorts of painful obstacles to solve the follow-on problems that result from their rigid (and arbitrary) dictates.
Understand Your Tools
It's sad that I have to write this, but the proliferation of crappy CSS classes convinces me that someone needs to hear it:
CSS classes are for repeated/re-used collections of style attributes. If you're never going to use this particular collection anywhere else in the app, then you're creating unnecessary complexity (and obscurity) by shipping those attributes off into CSS classes. If you're creating CSS classes that consist of a single style attribute, then you are the definition of "reinventing the wheel".
Inline styles are for adding single style attributes to an element. Or for adding an ad hoc collection of style attributes that are unique to this element. If this collection of attributes is not unique to this element, then bundle them up into a CSS class - and re-use that single class wherever necessary.
There... Was that really so hard???
Top comments (0)