CSS Classes Considered Harmful
keithcirkel.co.uk
keithcirkel.co.uk
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 extend selector solves the problem of one style definition inheriting styles from another style definition, but the author is trying to solve the problem of how styles are mapped to stateful elements. It's the mapping process that introduces the combinatorial problems of class names.
.my-card { width: normal; }
.homepage .my-card { width: bigger; }
.sidebar .my-card { width: smaller; }
(where 'normal', 'bigger', and 'smaller" should be replaced with real values).BEM suggested state labels (e.g. `.my-card__loading`). I'm on the fence about those vs `.my-card.loading`. The class-based syntax seems shorter than either BEM-style or attribute selectors, and thus I prefer it. You might get a bit more rigour from the more longer-form methods, but I'm not convinced the energy isn't better spent training your team to be more careful.
Shared code, CSS toolkits, shareable widgets present bigger challenges... the thing is that none of the tools suggested actually fix the problems. Your code can still break 3rd party code and vice-versa because of the global nature of CSS.
Specificity needs management, but so do type systems and class hierarchies. Stop pretending the cascade and global namespace don't exist and just do the work.
Naming things what they are and then styling with how they must look like creates an additional linguistic layer in an already csessed up system.
“What things are” must become component names, not a part of markup. If there’s no reason to componentize a chunk, it doesn’t get a name (and vice versa).
And also, I didn't say one should use `.emphasised`. I said to change the size based on the container: `.hero .card {}` would be big but `.articles .card {}` would be normal sized. `.card`s are always just cards; their context makes them different variations.
"But `.hero` is just another way of saying big, or emphasised"... Not really. A `.hero` is "the focus of the page. EVERYTHING (`.card`, `button`, `.btn`, `q` etc) inside `.hero` might all be big, or bold, or a contrasting colour, or surrounded by extra negative space, or in italic, or a mixture of those. Back when there were going to be aural style sheets [0], you'd have a way to specify alternate modality presentations using the same class names. It doesn't always make sense that something is aurally louder when it's visually bigger.
`.big` is not "what things are". `.big` is "how do we express what that thing is in this situation".
That's not "the web", that's "most humans"
Also computers were not black and white in the 90s.
I've kept hearing (and believing) this for 20 years but never seen a project where it's actually enforced. It seems like one of those things that doesn't work in practice and that come with very few benefits compared to the overhead it creates. Every time I redesign something I throw out both HTML and CSS anyway.
It's a very nice wrapper around CSS that make CSS feel so much less archaic to use. Do use SCSS though, not the weird SASS syntax which differs just a bit too much from CSS to be useful.
It's literally impossible to implement because humans just don't function like that. I'm sorry.
The answer for discoverability on the Internet was not semantic URLs or even link directories, it was the search engines.
I would love to do that, but it's simply not practical, at least not without CSS hacks that are hard to reason about.
---
A few reasons (non-exhaustive list):
In general, the separation of HTML and CSS is theoretical, not practical. They are tightly coupled descriptions of your overall layout, which is why there are so many incomplete attempts to manage the resulting complexity (see: this article).
HTML is not just content. It complects content plus layout. If you separate content from layout you get something like XML+XSLT, not HTML.
HTML is a strict tree structure and CSS can only query it in two directions: "down" and "next". A lot of layout and styling concerns are graph relationships. You need to adorn things _up_ the tree (in the form of nodes/attributes/classes etc.) in order to give enough context for layout and styling down the tree. And in some cases that doesn't work either.
Many CSS properties describe styling relationships between nodes. For example flex and grid make no sense in isolation. This couples the structure of your HTML with its styling in a profound way.
Some HTML tags don't represent their structure well, for example: <dl>, <dt>, <dd>. The natural way of thinking of these elements, laying them out and styling them, is to group each <dt> and <dd> together.
---
I agree half-way with you though. It's often useful to generate _just_ the "semantic" content at first: just use the tags and structure you need to lay out the structure of your content in a meaningful way. But as soon as you start caring about design, you will have to add tags, attributes classes etc. that are only there to serve styling.
:has() has entered the chat.
With recent display roles (like grid, flex, contents, etc.) the HTML needed exclusively for layout problems is really minimized if not completely nonexistent.
With `:has()` you can query "up" and "previous".
Some structure in your HTML is absolutely necessary, yes. But I'm not sure it is a problem. Data, content and states have structure.
Classes and attributes should be used for styling purposes, yes. Namespacing and linting can do most of the heavy work of keeping things tidy.
I'm not talking strictly about _problems_ but about requirements/relationships. Flex/grid describe parent/child relationships between elements. They inherently couple styling with structure, so that you are sometimes forced to introduce tags for stylistic grouping.
> With `:has()` you can query "up" and "previous".
:has() is a lookahead.
Please correct me if I'm mistaken: But there are no selector rules that you can put on an element which looks up or at previous siblings that I know of.
The only CSS features that query up/previous are @media and @container rules.
And yes, you can select previous elements with :has(). Here is an article about it: https://tobiasahlin.com/blog/previous-sibling-css-has/
Yes
> HTML is not just content. It complects content plus layout
No. One broad and now to a great degree complete goal of CSS has been: "regardless of HTML markup order, we can present these elements to the user in whatever order we want."
But it's not possible to write HTML agnostic to how it's intended to appear, in the general sense. That's the issue with CSS. This whole thing about separating content and styling is carried too far leading to very bad UI architectures.
Stuff like colors and fonts can be separated from content. Stuff like positioning, hierarchy, etc. cannot be meaningfully separated from content in many cases, and trying to do so is the root of UI evil.
This is why the component approach is so powerful. By combining content (with its implied style) with explicit styling in an integrated package, large UI trees become composable and easier to reason about.
But many devs still don't grasp this, and continue to fantasize about pure content that can exist totally separate from presentation, a pipe dream.
Applying styles with "form" classes like card still is semantic too!
Clear, correct, semantic HTML is important to people who want information to be shared as effectively as possible, who want collecting and processing information to be done easily, and who value correctness.
People trying to make a product don't give a rat's ass about correctness. They don't care if the button is actually a span with some weird inline CSS rules, they just want the blue "buy now" button to be front and center.
Semantic CSS is correct and it's the theory behind web browsing, but priorities have shifted. The web has moved away as a method to share information and has become a method for making apps and interactive experiences. There's no business incentive for correct HTML and CSS, only for the buttons that make money to render correctly.
Lots of team leads don't focus on these solutions and their benefits but rather on the most recent technologies and their own benefits while ignoring (or accepting as inevitable) their costs. The longer you look in that direction, the harder it gets to see what you are missing or could get by looking elsewhere.
Accessibility is one area I've seen an overlap. It is still rare, but I have been on a couple teams where accessibility was a metric and it largely lead to better DOM structure and semantic HTML.
If nobody "does it right", maybe right is just wrong?
It is certainly possible. However, what I have seen in the industry is that there are very few real CSS experts among people who are "busy working on actual software". Few people who have thought deeply about their methodology, the problems that it solves, and the problems that it introduces. And, on the contrary, there are too many people who either regard html/css as a playground for juniors, or are too happy to copy what other people are doing. In such context, it is very difficult to have a well-informed discussion about what is "right", and what is "wrong".
I will say HTML is flexible enough to be used for just about anything, but it certainly is better suited for text documents than web applications.
On a sidenote, do you know about HTMX? If so, what do you think about it?
HTMX is the same story—it's what HTML could be, but isn't, unless monkey-patched in userland.
For a while it was all left to appdevs to build a UI toolkit out of basic HTML but OpenUI really has been driving the standard forwards.
I'm not sure if I totally agree with this take, but if it's true that's a great reason to not build web applications. If the medium isn't right for the use case, why force it?
In my experience, application-specific features are really what made the modern web complex and bloated. Building applicstion frameworks and operating systems is insanely difficult, much more so than building a standard document format and rendering protocol.
Can the web be used for applications? Absolutely. But my argument would be that it almost never should be. Native applications will always handle that better, and though there isn't a great cross platform solution for applications today we'd be better off building that than shoehorning that usecase into the web.
Compare to the web: A single platform to build for. A single API to keep track of. A sandboxed runtime environment with zero dependencies, available on virtually every client system. A way to communicate with your servers that works, even in tightly guarded networks. A giant amount of documentation, available developers, and resources.
In an ideal world, I'd like to see native applications for everything. In the world we have, constraints prohibit that. So for better or worse, the web has to be mended into the universal application runtime environment we use it as.
After trying to make the most basic version of this app I can imagine, here's my takeaway on desktop application development: it sucks. Badly. Displaying a grid of images in a webapp is a few lines of javascript and a few lines of html. In most native application development systems I've seen, it's 20 lines of code just to open the window.
To be fair, the web makes easy things easy and hard things impossible, whereas native desktop applications make easy things hard and hard things possible. But, I definitely see now why most developers prefer to build things in electron.
If I need to do anything related to complex state changes, animations, page navigations, etc I'd also much rather use a native applications built with that scenario in mind. There are a ton of things we simply can't do with CSS - we're starting to see experimental features for simple page animations but they're very limited and manual. I worked on the Windows Phone UI framework back in the day, that was over a decade ago now and the kinds of navigation animations you could do with a few properties and flags was light years ahead of web page transitions today.
In what way do you mean this? HTML doesn't have the elements required for apps? Or declarative, XML-ish markup doesn't work well for apps?
I think that mostly shows in the awkwardness of transferring user interface widgets over. Stuff like popover, modals, combo boxes, toggles, drag and drop, sticky elements, etc. have either just recently become possible or require manual efforts to get right. And that, in my opinion, is an effect of shoehorning application primitives into a document environment.
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.
Especially with CSS pre-processors (because they make writing that syntax in the CSS file super easy) which basically everyone is using, ".card.card-big" is the obviously right solution.
It is easy to read, specific, doesn't risk collisions, doesn't risk adding broken styles if you forgot the "card" class on the element, etc. It addresses all the negatives outlined in the "BEM is not the solution" section of the article.
The only "argument" that still sticks against this one is that "it's verbose", but not once in my career have I seen a __good__ software engineer discard a perfect solution because "it's verbose".
Well, no, they can't. That's the point the article is making.
Classes can compose arbitrarily, i.e. class="size-small size-big"
Whereas data-size="small big", while valid syntax, would not be styled by a rule targeting elem[data-size="small"]
At a surface level `<div class="Card" data-size="big">` isn't really any better `<div class="Card big">`. But `<div class="Card" data-size="big" data-size="small">` is not valid HTML - the second attribute is discarded. While `<div class="Card big small">` has no such contract, and as such is valid HTML and the CSS is unlikely to account for it, perhaps doing surprising things.
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.
The rise of React and SSG/SSR means there is a world where the classes are React components and the attributes are props.
Even for static pages, for DX you can use the same tooling as you are used to for dynamic pages. And something like NextJS can generate the static html.
And this makes even more sense for dynamic stuff like date pickers.
In this world you use utility classes because tags are too granular and components are the logical unit.
But if you are hand crafting the html then tailwind might be more of a pain. I am a bit undecided on that. I still have a soft spot for CSS Zen Garden.
This is true about most things in tech though, yes?
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.
If anything, CSS is kind of an escape hatch out of HTML/SGML attributes which were specifically introduced for attaching any meaningful info to text nodes not displayed as such to the reader. This is especially evident if you compare the enormous amount of extensions and added non-type-checked ad-hoc syntax to CSS with the relative lack of HTML evolution. The only reason appears to be that HTML was organisationally locked in a W3C process for such a long time, while CSS could be easily extended.
Attributes are used for rendering details all the time with the original concept of markup. The idea that you need a special syntax and separate item-value space that's neither here nor there, like CSS is, when organising item-value assignments is the entire point of attributes and even hard-code a vague structure/presentation dichotomy that's then up for interpretation for like 30 years is beyond absurd. SGML, from before 1986, can already assign attributes (so called link attributes) based on context-dependent rules much like CSS.
Alas, people are too deep in tailwind for that :-(
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".
There is also a proposal in the works to allow web developers to define custom attributes - much like custom elements - which would likely follow the same or similar rules around dashes, at which point I imagine the HTML spec would guarantee that no dashes would be used in "built in" attributes.
New attributes have been proposed that reasonably _could_ have had a dash, for example `popovertargetaction`, but instead they were compounded to one word precisely to cave out this path.
There are template languages that extend the HTML syntax but render to valid HTML, and I imagine this kind of guarantee of future naming scheme is important to ensure the extended syntax does not have the potential to conflict with new attributes added to the HTML specs.
> proposal in the works to allow web developers to define custom attributes
Looks like this is it:
Proposal: Custom attributes for all elements, enhancements for more complex use cases
https://github.com/WICG/webcomponents/issues/1029
Searching for "dash" does bring up a thread of discussion around whether to require dashes or not.
---
I wonder if starting the custom attribute with a dash is allowed or not. Searching around, I see colon ":" and underscore "_" are OK, but dash "-" or period "." is only allowed after the first character.
> Any namespace-less attribute that is relevant to the element's functioning, as determined by the element's author, may be specified on an autonomous custom element, so long as the attribute name is XML-compatible and contains no ASCII upper alphas.
https://html.spec.whatwg.org/multipage/custom-elements.html#...
XML-compatible attribute name:
NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040]
Name ::= NameStartChar (NameChar)*
https://www.w3.org/TR/xml/#NT-NameStartCharOne aspect of Tailwind that I'm not satisfied with is editor integration, in particular linting, hints, autocomplete. There are editor extensions for this, but it feels too cramped working inside the `class` value, a single space-separated string.
Custom elements and attributes could enable a better editing experience, for example autocomplete suggestions can be specific to attribute name; or if the attribute is known to have a color as value, the editor can provide a color picker to fill in the value.
“Considered Harmful Essays Considered Harmful”.
https://www.infoworld.com/article/2160788/why-extends-is-evi...
https://www.infoworld.com/article/2161183/why-getter-and-set...
Especially considering that Dijkstra didn't even coin the phrase himself.
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!
Sorry a bit rant-y.
Also, wouldn't you say that there are also many rules and guiding principles necessary to write good javascript? Genuinely curious here.
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. Note: Because CSS gives considerable power to the "class" attribute, authors could conceivably design their own "document language" based on elements with almost no associated presentation (such as div and span in HTML) and assigning style information through the "class" attribute. Authors should avoid this practice since the structural elements of a document language often have recognized and accepted meanings and author-defined classes may not.
https://www.w3.org/TR/selectors-4/#example-f9c08b5b"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.