Why You Should Ditch Your CSS Framework

Writing Reusable Styles

The New (Old) Trend

Every developer remembers the dark days of floating left and right, clearfixes, and very specific column widths that never quite added up to 100%. Then along came the superhero that took all this hassle away from CSS - and its name was Bootstrap. It was amazing! Even beginners or back-end devs could just add a couple classes and the enigma of CSS layouts was a thing of the past.

Meanwhile, this person named Tab Atkins had noticed this issue as well, but they were in a position to implement new native CSS properties. This is where grid  and flexbox  enter the equation, forever putting floats and (god forbid) tables to rest. After Internet Explorer was safely removed from most projects' requirements, CSS was finally able to accommodate layout concerns with relative ease and flexibility.

So this is the end of Bootstrap's story, right? Well, to only mention layout would be a crime against Bootstrap, since it also included several CSS components, an icon library, and more. However, it wouldn't be farfetched to make the claim that most people implemented Bootstrap specifically for its easy grid layout. And, at this point, people had already lost faith in native CSS after struggling with it for so long in the past. This marks the beginning of the era of CSS-phobia. You could only pry Bootstrap from these devs' cold, dead hands.

But why would you want to? I mean, it works, right? Well, it wasn't free of its own issues. Most notably, the HTML became nearly unreadable, not to mention unmaintainable. Once you wrote your class names, there was no redesigning or theming your page, it was stuck that way unless you spent hours doing surgery on your markup.

Naming conventions like BEM dictated a way to keep your class names semantic and specific, so that you could always restyle your page without modifying the HTML. This also stuck more closely with the philosophy of separation of concerns , which states that we should keep our markup free of presentation when possible. In fact, it's even explicitly stated in the HTML5 spec that class names should reflect the nature of the content. After several years, it seemed the battle was finally over. The consensus was, for the most part, that Bootstrap was not a suitable production solution.

... But then along came Adam Wathan to take the baton and run with it. He argued that "separation of concerns" was the wrong issue, and that we should instead focus on "dependency direction." Either your HTML is restyleable, or your CSS is reusable, but they're mutually exclusive. He cites Nicolas Gallagher , who states things like " Class names impart little or no useful semantic information to machines " and " The primary purpose of a class name is to be a hook for CSS and JavaScript. " If I understand Wathan's opinion in this context, he thinks it makes more sense to choose reusable CSS, especially since there is little to no impact on the content by class names anyway. He then concludes his thesis by introducing his own CSS Framework, Tailwind . This takes a slightly different approach to Bootstrap, focusing more on the concept of utility classes and composition, rather than full CSS components.

Wathan's challenge to the W3C reinvigorated those frustrated with CSS world wide, who praised their new savior and hailed Tailwind as the future of styles. Of course, I don't fault Wathan for this, in fact, I admire the audacity to challenge common wisdom and even the authors of the spec themselves. We need this kind of discourse to continue improving in new and innovative ways. The fact that he was able to craft a solid tool and contribute to the industry is evidence of his skills, and I would certainly never accuse him of being incompetent. That being said, I personally remain unfased by his arguments, and I'll explain why.

Why Are Semantic Class Names Important?

I admit, it's a particularly compelling point that class names don't have any semantic meaning to machines. To them, they're just strings of text that will never show up in search results nor be read aloud by assistive technologies.

That being said, the main concern is not how the machines interact with them, but rather the developers. There are some foundational concepts of computer science we should seek to uphold, like modular programming and information hiding . Each part of your software should be completely independent and swappable, while remaining easily modified from as few places as possible. Generic abstractions provide more flexibility to plug and play new modules without disrupting the other parts of the code.

In other words, class names shouldn't couple your HTML with anything in particular, but instead they should be implemented with the purpose of isolating your design concerns. As an added bonus, it becomes easier to understand the abstraction from CSS; author-headshot  is much more useful than card-img-top .

On the flipside, writing class names which describe the presentation make your HTML less meaningful to developers. Imagine being the SEO specialist who was hired to optimize all the content when there are an average of fifteen unhelpful class names on each tag. Ick, not fun.

Now, you may be thinking, Tailwind has @apply  for composing utility classes within CSS! To which I'd say... that's somewhat acceptable, but at that point, why bother? Each utility tends to define only one rule, maybe two. There's hardly an advantage in writing @apply p-4;  when you could just write padding: 1rem;  instead. Or, if you're worried about consistency, perhaps padding: var(--p-4); . But I digress, more on this later.

What About Reusability?

According to Wathan, reversing the dependency direction just means you will have to start duplicating styles in lieu of disposable markup. However, his examples leave some pivotal information out. When introducing two components that are similar, he suggests three options:

  1. Duplicate the styles

  2. Use a preprocessor for @extend  or a mixin

  3. Create a content-agnostic component (by choosing a less specific name appropriate for both)

But he forgot #4, which frankly should have been #1 - use , .

.author-bio,
.article-preview {
  background-color: white;
  border: 1px solid hsl(0,0%,85%);
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  overflow: hidden;
}
.author-bio__image,
.article-preview__image {
  display: block;
  width: 100%;
  height: auto;
}
.author-bio__content,
.article-preview__content {
  padding: 1rem;
}
.author-bio__title,
.article-preview__title {
  font-size: 1.25rem;
  color: rgba(0,0,0,0.8);
}
.author-bio__body,
.article-preview__body {
  font-size: 1rem;
  color: rgba(0,0,0,0.75);
  line-height: 1.5;
}

/* and for composition... */
.author-bio {
  background-color: blue;
}

This example stands without any templating solution nor CSS preprocessor, yet it involves zero duplication, and even some composition.

Of course, there's even a #5 beyond that - use a tool like CSS Modules with React, Vue, or something similar to scope your styles to each component. That way, you can use component composition to your advantage, and you'll never have to duplicate any of your styles again.

// =========== authorBio.js
import css from './authorBio.module.css'
import MediaCard from '../mediaCard/mediaCard'

const AuthorBio = ({ children }) =>
  <MediaCard className={css.authorBio}>{children}</MediaCard>

// =========== articlePreview.js
import css from './articlePreview.module.css'
import MediaCard from '../mediaCard/mediaCard'

const ArticlePreview = ({ children }) =>
  <MediaCard className={css.articlePreview}>{children}</MediaCard>

And there you have it. Reusable styles without sacrificing restyleable HTML. Sometimes, you can have it all.

Increased Speed Is a Myth

A common rebuttal to all this concerns how much faster a team can build something using a framework. In a world of tight deadlines, we can't always use the best approach, because the priority goes to the fastest one.

While I agree that a team should always choose the technology the members are most familiar with, I want to set the record straight. Tailwind can only be faster if you don't know CSS well enough to go without it, otherwise it's equally as fast to write vanilla CSS. The classes in Tailwind represent each rule in CSS nearly 1:1, and at that point, why not just write the rules yourself?

Becoming comfortable with CSS should be the responsibility of every dev who must touch styles as part of their job. By introducing a framework, you effectively shift the burden of a learning curve to someone else.

Bootstrap may save some time if you use their out-of-the-box components and you don't have a designer to satisfy, but then again, that rarely happens in real-world situations. Realistically, every business has branding and design requirements, so you're going to have to make a lot of customizations at some point.

More recently, many folks have begun treating these frameworks more like boilerplate themes, making modifications directly to the original style rules as opposed to overriding them. Again, this may save some time assuming the components are similar enough to your design that you are able to narrow it down to a few tweaks, but how valuable is the documentation if you're changing the source code?

On the other hand, CSS components specifically intended for layout (like col-md-6  for example) would not save time in the same way. Generally, you will not need that many <div>  tags to accomplish a simple layout - as long as you aren't still supporting IE, but that's a whole other topic . In general, it's agreed that it's not great to rely on column classes. One could argue that introducing the temptation to break the rules in a team setting is a little too naïve, although you could just remove the grid classes... but then you're back to invalidating the documentation.

All this to say, it adds up to be a lot of rules, exceptions, and extra steps. In my own opinion, I don't find the tradeoffs worth it.

Anecdotally, I can say that I've never yearned for a pre-styled component when building a new website. The most common building blocks, like buttons, links, titles, etc, have never been too much effort for me to write from scratch, because they require so few styles. As for the stuff that does require a bit more effort, the complex designs provided to me have always looked far too unique to build up from any generic components. So, at least in my case, it would cost me more time to remove the unwanted styles than it would to just start from scratch.

Documentation Isn't Necessary

It is true that institutional knowledge should never leave with an ex-employee, and third-party documentation is a great way to avoid that. Bonus points if it's popular, so you can always look up answers when the docs just don't cut it.

But let's be real - if a stylesheet becomes that complex, it's more likely a sign of tech debt than something you forgot to document. In JavaScript, it's not fair or reasonable to tell someone to "just make it simpler" or rely on "self-documenting" code, but CSS has one job, and the role that it plays is not meant to be complex.

Just a reminder, I'm not advocating for a custom framework or library either. A massive global stylesheet will certainly lead to problems. I think scoping your CSS to each component is the most ideal solution; encapsulation and composition, always. If you're paying a developer money, they should be savvy enough to work with one tiny CSS scope at a time without any documentation.

As long as you avoid too much specificity and excessive overrides, CSS documentation should almost never be necessary. If those exceptions are a real thorn in your side, then you can write (or adopt) a styleguide, add linters and precommit hooks, have more strict code reviews and PR approvals... there are a plethora of measures you can take that don't involve inheriting all the problems that come with a framework.

Consistency and Reusability

So you want to maintain a look and feel throughout the website, and thus you don't want to allow each team member the freedom to invent their own unique padding size. I get that, and it can be tempting to reach for a utility class to take care of that for you, but bear with me. It may not be obvious right away, but extending or applying that class actually obfuscates vital information and reduces some reusability. .p-4 , as we mentioned earlier, will apply padding: 1rem , but what if you want the gap  property to match? You can't reuse .p-4  to get you there, but you could reuse a variable.

.article {
  display: grid;
  padding: var(--size-4);
  gap: var(--size-4);
}

Not only is this more reusable, but it's also easier to read. "Blasphemy! There's more text!" I should pose the question, is it more important to be brief, or to be understood? .p-4  has no meaning unless you already have that esoteric knowledge, but reading the word "padding" makes everything a lot clearer. It also means that I don't have to search for Tailwind documentation to understand what properties are being impacted by what classes, which can be quite tedious when there's a lot of them.

Conclusion

I'm certainly not here to disrespect Wathan or Gallagher, as I said earlier, I actually admire them. They're out there making waves and that in itself is exciting for the future of front-end development. Both their words and my criticisms should all be taken with a grain of salt. Do your research, keep an open mind, and let the truth be your guide.