July 17th, 2024

CSS Classes Considered Harmful

The use of CSS classes is criticized for limitations and conflicts. History of HTML/CSS evolution is discussed, proposing attributes over classes for more flexible styling in modern web development.

Read original articleLink Icon
CSS Classes Considered Harmful

In a post by Keith Cirkel, the use of CSS classes is criticized as outdated and problematic due to their limitations and potential for conflicts. The history of HTML and CSS evolution is briefly discussed, highlighting the introduction of class attributes in HTML 4.0 to style elements. The challenges with using classes to represent component states and parameters are outlined, leading to issues like specificity conflicts and scalability problems. Various methodologies like BEM, Atomic CSS, and CSS Modules are examined, each with its drawbacks in addressing these issues. The proposal suggests using attributes instead of classes to parameterize components, resembling a key-value representation like a Map. This approach aims to provide a more robust and flexible way to style elements without the limitations of traditional class-based systems. The article emphasizes the need for modern web development to move towards attribute-based styling for more efficient and maintainable UI design.

Related

Link Icon 35 comments
By @donatj - 4 months
The problem is the modern webs hyper focus on appearance over content. Content first, style second.

I still say that the answer is just semantic HTML/CSS. You're coming at the problem from the wrong direction naming things for how you want them to appear. Name them for what they are. Write the HTML agnostic to how it's intended to appear, and then just use CSS as a tool to style the already existing document.

SCSS and extending placeholder[1] selectors makes this insanely easy and viable. You can write your "how things appear" classes as placeholder classes, and then simply extend those from your "what they are" classes. Super easy to keep organized.

1. https://naxoc.net/2014/01/28/placeholder-selectors-in-sass/

By @whywhywhywhy - 4 months
I don't understand how "<div class="Card" data-size="big"></div>" is better than just <div class="Card big">, then using a .card.big selector?

The article just says "but this can cause specificity issues, which can create problems further down the line" to move the same thing into attributes rather than just use the thing for what it was designed for there has to be a good reason.

By @hnthrowaway121 - 4 months
I don’t like the proposed method but I’m glad to see thoughts a little outside the main lines of utility CSS vs Module isolation vs naming conventions like BEM.

One bad consequence of the custom element name convention is that the outer layer of a component then has no semantic meaning and accessibility has to be fully implemented by the developer, which requires a lot of knowledge and effort compared to using correct semantic HTML.

Inaccessible code is a real business risk and making all components generic elements with no affordances for screen readers would not help. It’s a pity the article doesn’t consider this at all as its recommended patterns would lead to some needlessly bad experiences.

By @Tade0 - 4 months
Sounds like a call to return to the days of <P ALIGN="LEFT">.

I occasionally use attribute selectors - really useful when you have crazy requirements like "Here's a table with rows having generated data-index attributes. Make the background of each[0] prime-indexed row blueish".

For other uses classes are quite simply shorter.

Also, if I have to write a lot of CSS then either:

-The stakeholders insist on an "unique look", in which case kill me.

-I'm making a component framework of sorts. If it's an internal project, also kill me.

[0] well, not "each". Just the first 100 or so. Users don't read long lists anyway.

By @lrtz - 4 months
There's big performance downside replacing classes with attributes at least when using javascript query selectors.

Browsers have special data structures for id and class search optimizations.

Matching attributes scans through the dom top down.

By @sakjur - 4 months
Isn’t this mostly moving the namespacing problem from classes to tags and attributes? It’s not something I mind, but since the other potential solutions were dismissed as flawed for similar reasons, I’m not sure why the solution presented in the post is better than the other solutions.

That criticism aside, I learned something new about CSS today, and the dynamic attribute based values is something I’d like to play around with to see if I like it. I also appreciate the enumeration of different strategies, that is something I can reference in a conversation with a colleague even if my conclusion is different from that of the author. Thanks for sharing.

By @vogon_laureate - 4 months
This gives big bikeshedding vibes. I always liked the idea of semantic markup, but humans are messy and complex and we will always find ways to bring that chaos to utopian systems. Classes and IDs work fine most of the time. Documenting your styles is good practice. Using BEM or Tailwind has the benefit of adhering to a system and base classes that others can make sense of without much effort. CSS is OK. Our energies are better spent on more important things.
By @codeflo - 4 months
> Conceptually you could think of these as classical inheritance (so class Card extends Div) - inherit the semantics and base styles of div while making a re-usable style for a Card class.

You could, but you’d be wrong. The CSS selector specificity algorithm has nothing in common with OO inheritance, and classes are only one of many equivalent ways to interact with that algorithm.

By @tiborsaas - 4 months
All these examples of "scaling CSS" tends to end with "don't write CSS".

Relying on HTML attributes to describe presentation could have its neat uses, but suggesting it as a new generic paradigm that everybody should jump on regardless of problem space is not the way to go. HTML is not the escape hatch out of CSS.

The main issue is that it's just not better than a class soup of Tailwind classes. It maybe a bit more readable, but if you stuff a DIV with endless `data-junk` then it will be just as bloated as the rest of these specifications if used without care and if you avoid writing CSS it will be pretty bloated.

By @000ooo000 - 4 months
Good job everyone. We're almost back there again. Twenty years later, 2026 will be the year of semantic CSS, though I guess it will have a new, cool name. Complete Stylesheets or something I dunno I'm not in marketing.
By @skrebbel - 4 months
Wow I like this solution a lot, and I’m sad that it’s presented after a million paragraphs of ranting and a terrible title.

The tl;dr is: if you use custom element names (instead of “.Card”) and attributes (instead of “.Card—size-big”), things get very clear and you lose many downsides of other approaches (such as name clashes and unmaintainable soup):

So write HTML like this:

    <my-card data-size="big"></my-card>
And CSS like this:

    my-card { /* ... */ }
    my-card[data-size=big] { width: 100%; }
    my-card[data-size=medium] { width: 50%; }
    my-card[data-size=small] { width: 25%; }
Personally I think this idea is really great. I’m not sure yet that I prefer it over Tailwind in all situations, but it seems obviously better than eg BEM, CSS Modules, inline styles, styled-components and all those other approaches. I really love the idea, it feels like a “woa why didn’t I realize this before” kind of thing.

EDIT: Thinking on this a bit, I’m not sure I’d replace every single div with a custom element like the author suggests, I think a div-with-a-class works just as well and is easier to grok for other readers of the code cause everybody knows how a div works. Readers don’t have to figure out whether it’s a defined custom element (ie a JS class) or just a CSS level thing, etc.

But the idea of using attributes instead of .my-thing—-level-3 is genius in my book. It means you only ever need one class per element at most. No more concatenating long className props, no more repetitive prefixing, it’s scoped by definition, etc. Also all the logic in the HTML rendering code is nicely siloed:

    <div 
      className="my-thing"
      data-level={someFunc(props.foo)}
      data-size={props.size ?? "big"}
      etc
    >
So much nicer than some nested classname object hack!
By @lioeters - 4 months
Link to the CSS Values 5 specification, where it describes how HTML attribute names can be referenced in CSS properties.

6.4. Attribute References: the attr() function

https://drafts.csswg.org/css-values-5/

With the ability to specify attribute types, including dimension units, and to provide a default value - it does look like a new way to connect HTML to CSS beyond the limitations of classes.

---

> The data- prefix can be a little unwieldy but it allows for the widest compatibility with tools and frameworks. Using attributes without some kind of namespace can be a little dangerous, as you risk clobbering HTML's global attributes, but as long as your attribute name has a dash it should be quite safe.

I wonder if this last part is true. For custom elements, since they're required to have a dash, I imagine future HTML tags are guaranteed to not contain a dash. But for attributes..

Are all future HTML global attributes guaranteed to not include a dash?

Maybe the author means, as long as your custom attribute has a name with unique prefix, it should be "quite safe".

By @debesyla - 4 months
Clearly the best solution is just to write styles inline, because classes are evil and attributes are just classes with a hat.
By @mirzap - 4 months
How is using markup filled with data-attributes better than "messy" atomic CSS? I don't see how this is an improvement over simply using classes. Maybe it's just me.
By @beardyw - 4 months
The author seems to be under the impression that classes didn't exist before OO. Of course they have always been used to describe broad groups of things and in that context their use in CSS is perfectly consistent.
By @Etheryte - 4 months
This is just class names with extra steps and more syntax, it's a downgrade, not an improvement.
By @riiii - 4 months
If you're not Dijkstra then you're considered harmful if you steal his valor in your title.
By @easyThrowaway - 4 months
I mean, this could've been called "CSS Considered harmful". Also suggesting the use of attributes to mix state with style is... quite the bold strategy.

What if the same component is used multiple times in a page with a slightly different appearance and same behaviour? Do you keep adding more attributes? You're gonna end up rebuilding Tailwind, but using an even more opaque syntax.

By @Pesthuf - 4 months
In the CSS that we have today, they are still probably the least shitty solution in most cases.

attr() for things that aren't content isn't really supported and manually doing [data-gap="1"] {} [data-gap="2"] {} and so on sucks for various reasons. Attributes like [card-size="big"] look good, but unlike classes, they don't get autocompleted so you always have to remember the magic strings (ugh) and errors don't get caught by static analysis.

It's not just the class attribute that's harmful legacy garbage, but the core design of CSS. But we're stuck with it.

By @replete - 4 months
I've been building websites for over 20 years and I've come back to frontend after a few years out of the industry recently, horrified to see what developers are doing with current trend tailwindcss trend, but understand why you'd want it. I pity whoever picks up those legacy projects

I thought about using custom tags before for components (not web components, which have a few gotchas) but it came down to SEO. If there were more certainty on the impact of custom tag on SEO I'd already be doing this.

By @dzonga - 4 months
we really need a separation between documents and applications.

for documents - css classes & BEM are more than adequate.

for applications - we seem to have short memories. why not learn from what complex UI frameworks used in industry do e.g desktop frameworks case in point - https://doc.qt.io/qt-6/stylesheet-reference.html#background-...

By @catapart - 4 months
Lots to dismiss in this, but I was pretty impressed by the bit about psuedo selectors! I had no idea this could be done, and it's pretty awesome! Can't think of when I would use it, given that you have to publish your implementation details for it to be useful (even if you only publish them for your internal team). But for personal projects, or well-moduled projects, I might reach for that little trick in the future.
By @emmacharp - 4 months
For the curious, here is a set of "simple" rules for CSS authoring that, coupled with linting (through Stylelint, for instance) can address a lot (most?) of the issues raised in the article and the comments:

https://ecss.info/en/

Interested in what you may think about these rules and the principles behind them.

PS: still a work in progress!

By @lofaszvanitt - 4 months
CSS is like a wooden spoon.

Conclusion: you are overcomplicating things. This ain't rocket science. CSS is dead simple, only you making it complicated.

By @Xen9 - 4 months
The title is made by a person with very bad taste: Dijkstra would have given a different name to the title had he got a chance to return to past and his attitude to person writing articles about CSS would probably have been despising largely.
By @ericyd - 4 months
I expected to hate this article but ended up finding it interesting. The author didn't convince me but I thought they had style interesting points.
By @cbeach - 4 months
The author's solution mixes style declarations (i.e. centering text) into HTML, which surely goes against the spirit of CSS?

    .Card[data-align=center] { text-align: center; }
Filling HTML with data-* style attributes would intertwine layout and content in a single file, meaning we're no longer able to use CSS to flexibly re-style the content.
By @floppiplopp - 4 months
don't write css in the class attribute, write the css in a custom attribute. even better, implement a custom element, that uses custom attributes! how totally brilliant, absolute genius, what a visionary!
By @meerita - 4 months
I complete disagree with this statement that lacks evidence and it just want to duplicate functionality:

"At first blush a utility class system might seem like a boon to a design system, but when applied to the markup we quickly see the problems: being unable to represent components easily in markup leads to a design system looking for other solutions such as providing markup with attached class names to represent a component - which usually results in the design system implementing components across a multitude of frameworks."

In this era of component frameworks, looking at HTML and its classes is the least of our problems. Our real challenge is to build an additional architecture on both the component side and the CSS side. Additionally, both components and CSS can have (or end up with) more than just styles; they can include behavior, animation, and effects, which adds complexity, making the entire project difficult to memorize, scale, and maintain. OOCSS projects often result in unmaintainable code, with developers ending up with large CSS files and creating more specific classes to implement new features. FCSS (Functional CSS, or Atomic CSS as exemplified here) solves this problem by separating concerns and removing the mental load from the equation.

The problem with Atomic CSS, Tailwind and others I just the syntax. It takes time to master Tailwind toolset of classes. At first it may seems easy and intuitive but it’s deceiving. FCSS has natural language https://www.fcss.club/syntax and it’s hundreds of times more intuitive.

The only real issue of FCSS or Tailwind is achieve inheritance only using CSS inheritance model. Let me give you an example:

.color—blue { … }\ .color—red { … }

<div class=“color—red color—blue”>text</div>

In this situation, you might expect the text to be blue, but it will actually be red due to inheritance weight. This issue can be resolved on the component side easily with JavaScript https://www.fcss.club/customization, providing a more controlled solution.

“There are a plethora of other issues with the Utility CSS methodology, and with it a plethora of articles. If you consider this a suitable solution, I'd encourage you to invest time researching the pitfalls, but I don't want to spend too long on this."

I’ve been reading dozens of articles regarding the atomic approach and I didn’t find any phletora of issues. The only thing I found was a bunch of arguments in favour of OOCSS, all of them listed here. https://www.minid.net/2019/8/12/in-defense-of-functional-css

By @bythreads - 4 months
Just use web components, and use trie attributes instead.
By @HelloNurse - 4 months
"Scaling class selectors" is a false problem: actually, classes and pseudo-classes "increase the granularity of control over elements" (which are much closer analogues of classes in the OOP sense than CSS classes) better than in 1997 because useful new selectors and pseudoclasses have accumulated over the years.

Class selectors (and everything else) become weak, as should be expected, with monkeys ("With mutually exclusive classes like Big and Small, it is possible for elements to apply both classes at once") working with no actual design ("parameterise Card to take a size option which is either Big Medium or Small, a rounded boolean, and an align option which is either Left, Right, or Center").

By @mattkenefick - 4 months
It's unfortunate how many people misunderstand CSS today and how web applications should be structured.

Articles like this make me sad.

By @dsego - 4 months
> Now your design system team is tasked (or burdens other engineering teams) with orchestrating all of this tooling.

Design system team? I'd like to have that problem.

By @peterlada - 4 months
No. I mean data-no=jose