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 articleIn 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
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/
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.
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.
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.
Browsers have special data structures for id and class search optimizations.
Matching attributes scans through the dom top down.
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.
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.
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.
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!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".
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.
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.
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.
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-...
Interested in what you may think about these rules and the principles behind them.
PS: still a work in progress!
Conclusion: you are overcomplicating things. This ain't rocket science. CSS is dead simple, only you making it complicated.
.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."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
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").
Articles like this make me sad.
Design system team? I'd like to have that problem.