1. Introduction to Atomic CSS and Scaling Issues
Hello, everyone. Today, we're here to discuss the traditional problems of scaling CSS, the lack of namespaces, CSS being stuck in the past, its unpredictability, and its static nature.
Hello, everyone. It is great to be here in React Summit, joining all of you online. And today, we're here to finally talk about atomic CSS.
Before we move forward, this is me. You can find me everywhere as wide combinator. I'm a Senior Software Engineer at Medallia, a mentor at TechLabs, and a Google Developer Expert in Web Performance. And the link for this presentation deck you can find under this QR code and the links to many other presentations I also have.
So, today we're here to discuss, first, the traditional problems of having CSS at scale. Some of what I consider to be bad taste on the topic of atomic CSS, tailwind, et cetera. And then what I actually consider to be bad and ugly about atomic CSS as a methodology. But we're also here to discuss how you can address some of these problems, and then we'll have some closing thoughts about everything.
Starting with problems of scaling CSS, I guess that the cliche is the first one. So, the lack of namespaces. So it started as a feature in CSS to enable portability and the cascading part of CSS, but if you're doing other programming languages, you notice that if you lack built-in namespaces, you get to have problems at scale. And throughout the history of CSS, we've had different technologies trying to address that like CSS in JS and others that basically would use VLTools to intercept and generate scopes automatically.
Another key problem which I consider is CSS tends to be stuck in the past. That's mostly because you have to maintain backward compatibility as a top priority when you're building new features for the language, for the spec. And because of that, it's not always feasible to just go back in the past and undo bad decisions. And that happens because, yeah, you have to keep adoption rate in mind, you have to keep browser support, everything when making changes to the language.
Another thing is it's unpredictable to humans in a way. So the way CSS works is it merges styles coming from different sources, different origins. I'm talking like inline styles, internal and external stylesheets, a whole bunch of selectors you have, et cetera. And to properly figure out how this happens, you have to be aware of the specificity calculations, how inheritance work and many others. And that's quite complex for humans. So in the end, it's hard for us to predict how those styles are resolved.
2. Atomic CSS: Advantages and Addressing Concerns
So, Atomic CSS is often compared to inline styles, but it offers advantages such as better performance, handling media queries and pseudo selectors, and a well-defined API. Concerns about HTML bloat and the breaking of separation of concerns can be addressed with modern compression algorithms and a shift in perspective. By using atomic CSS, you can assemble pieces from a style sheet in the HTML, providing an API and avoiding impractical scenarios. With modern tooling, atomic CSS can alleviate concerns and bring new benefits.
So, that's challenging. So, I'd say that those are the four main concepts we have.
And now, moving on to Atomic CSS, the core of this talk. So, I will try to do a good, bad and ugly approach here. But, if you're probably tired of listening to this multiple times, I wanna show you, I wanna do a different take here. And I'm gonna start by going through what I consider to be actually bad takes people have about Atomic CSS. And the first one is, they compare a lot with inline styles. But the thing is, first, inline styles are really costly for the browser, because it's just really costly to convert. The strings you have onto the style attribute and to native representation in the platform. And also, by adding those multiple styles, you are adding multiple reflows. Also, inline styles can't handle media queries, pseudo selectors, and other features you have. And one key point here is, mostly when you're doing inline styles, you're gonna have to follow preexisting definitions as you would have with utility classes, where you have a very well defined API, and those act as a single source of truth.
The other point we listen a lot of there is, it generates HTML bloat, and that's bad for performance. But, the thing is, we nowadays have a lot of compression algorithms, like GZIP, and we even have techniques made to handle duplicate strings like the deflated algorithm. And, I also drew another perspective in that. That is, the more a selector is repeated in the style sheet, actually the more work a browser has to do to resolve those styles. So, in that sense, semantics CSS and others could also be adding a bloat.
Then comes the concern of separation of consorts. And, a lot of people point that atomic CSS by definition break separation of consorts, but I think that we should kind of shift our view and see that the style composition is being performed in the HML, but you're not doing style align with the height, those attributes. Actually, what you have are just pieces from a style sheet that are assembled in the HML. So, the HML is kind of the consumer of the CSS you created. So, you have an API in that sense. And, also I think that this kind of represents this very extreme vision of what separation of consonants is all about. And, that's dangerous because that can lead to very impractical scenarios. And, this is even addressed, for example, in the Vue.js documentation where they point that a lot of people make a confusion between separation of consonants and separation of bio types, for example. And, in an analogous way, you have a bunch of other things like redesigning and faming becomes challenging or you end up with a lot of unused CSS or it's hard to use what's available, it's hard to know what's available there for you to use or even you have to learn a whole other language on top of CSS. And, the list is huge. Point is, I think that those are concerns kind of from, coming from the past. And, if we look at tail end and modern tooling we have in the scene of atomic CSS these days. We have the potential to alleviate most of those concerns and even get new benefits.
3. Critiques of Atomic CSS
Atomic CSS suffers from bad claims since its initial posts in 2013. The assumption that switching between HTML and CSS all the time is necessary is dangerous. There's a popular belief that Tailwind is the future of web development, but it has a utility CSS lock-in. Converting Tailwind into another framework requires significant refactoring. Tailwind sometimes lacks key CSS features like PseudoElement and selectors/functions.
So, now that we took those out of our way, I think we can start talking about what I consider to be bad about. So, I like to start with the bad claims on the topic. And, I think that atomic CSS suffers from having bad claims coming since its initial posts back in 2013.
So, for example, you will see something like, simple changes to the style of the module have resulted in new rules in the style sheet. But, if you think about it, CSS is supposed to define the style and the layout of the page. So, if you're modifying the appearance of UIElements, it is okay that you are required to do tweaks in the CSS. This doesn't mean also that you're creating new classes. And, in the tailwind, documentation is the same. So, it starts with rapidly build modern websites without ever leaving the HTML. But, I think there's a dangerous assumption being made here that is switching between HTML and CSS all the time is a thing. But, this could be actually a flag. If you're tweaking markup constantly while you're working on the appearance, this could be a sign that you're treating the markup as an extension of the CSS rather than the markup for the structure of your document.
And, from a lot of those technologies that have to do with dynamic CSS, you will see those claims, like best practice, don't work, and that kind of stuff. And, I noticed that there's this popular belief that tailwind is actually the future of the whole web development. And, if you're not doing it, you're not doing web the right way. Another thing is what I like to call the utility CSS lock-in. So, if you think about semantic HTML on CSS and you wanted to do the magic exercise to convert that into tailwind, what you would have to do is to break those compound semantic classes into atomic utility classes that would have single responsibilities. And, that's kind of an easy thing to be done. And, you even have tools out there, for example, Windy, that convert semantic CSS into atomic with tailwind. But if you think about the opposite, it's hard for you to picture a tool that does this because you can't really build anything more complex without assembling those classes by hand. So, one could only realistically convert tailwind into some other utility CSS framework. And that's tricky because if you're moving to any other framework or preprocessor, you would have a considerable amount of refactoring. And I know that most of us aren't doing this, we are not swapping out CSS frameworks or CSS as architecture standards on a regular basis, but it's an important thing for you to take into consideration for apps that will be maintained in the long-term.
The other thing, this is mostly on Tailwind rather than the whole methodology, but I see sometimes missing key CSS features. For a while, that was the case with PseudoElement. So almost after two years of the initial release of Tailwind, it still didn't support PseudoElement and some other features. And that's something that many CSS preprocessors have supported for a while. And then it was the case for background gradients and for animations. And nowadays it is the case for selectors like is, and where, or functions like min, max, and clamp. And that tends to be the case for any other bleeding edge features.
4. Tailwind's Class Detection Mechanism Limitation
Tailwind's class detection mechanism uses regexes to track unbroken string class names, which limits the ability to construct class names dynamically.
So you will have to come up with workarounds or for a while handle the lack of them. And the last one is also more tailored to Tailwind is their class detection mechanism. So the way Tailwind works is it doesn't parse or execute any of your code. So it uses regexes under the hood to track every single string that could be possibly a class name. So that means it will only find classes that are unbroken strings. And because of that, you can't construct class names dynamically. And I think this is kind of limiting. And this is not actually a surprise. This is even stated in the docs. But that's something I'd like to see change from a developer perspective using Tailwind.
5. The Ugly Side of Atomic CSS
And then we find that they get to talk about the ugly. It's hard to scan long class names and increment them with media queries. Atomic CSS focuses on classes, which can lead to large nested DOM structures. Thinking in CSS classes first can lead to suboptimal markup. Tools can make it easy to do things in a non-ideal way. This can result in excess markup and negatively impact performance, readability, maintainability, and accessibility.
And then we find that they get to talk about the ugly. And again, I have to start with a cliche that is, I still find it really hard to scan. And it's actually hard for you not to agree with that. If you have that kind of class names and you want to, for example, increment that with media queries, this becomes fixed. And if you compare it to what would be, for example, semantics, an alternative in semantics CSS, you would have something like this where you have a class and then you have the traditional media queries for the different screen sizes you support. And let's say in a scenario that you are tracking down a UI bug and you know it is not related to the desktop or the tablet version, you can ignore the blocks of the media queries and focus on your class, for example.
So it all happens because it's simple. It's simpler for us to scan if we need to read code from the top to the bottom. And it's easier for our eyes to find specific pairs of properties and values. So if you have proper syntax highlighting, separating those properties and values, you even have better readability. And you can say, okay, but those long class names don't happen a lot up there, but it turns out that they do. And it's coming for you to see a lot of production websites that have like 60, 70, even more class names. The other thing is it's kind of this culture of encouraging large soups of tags in the markup. So I think it happens because with Atomic CSS in general, the focus is on the classes not the markup.
So before we move forward, don't get me wrong, Tailwind or Atomic CSS, they don't promote ugly HTML. But the thing is with this mental model, you can throw in as many divs as you want and you don't have to necessarily care about the meaning. So you end up in this area where you can do things the right way, but because it's easy you tend to end up with large nested DOM structures that sometimes are not even meaning semantic elements. So this is for example, a actual markup from a footer in Tailwind UI template. And so it happens a lot up there. And if I were to come up with a hot take on top of that, I would say that the problem here is you're thinking in CSS classes first rather than starting with the markup and doing CSS from there. And following up on this hot take, I think that tools in general, they can make it easy to do certain things in the not ideal way. And I think that's what's happening here. An approach that makes for semantics the easiest path. And I really like how Tony Yeliseya defined this in terms of a mindset that, okay, I need somewhere to put my CSS classes. So let me just add another div. And I ultimately understand that this is probably for optimizing for developer happiness and productivity, which is a strong point in those methodologies, but I'm afraid by doing so, you could be optimizing over performance, readability, maintainability and accessibility. And more and more, you go through those open repositories of sharing snippets of doing, you will see like those excess debated examples like this one, where you have an endless number of this, you have, for example, an empty heading, you have placeholders doing what labels are supposed to be doing, you have spans that should be headings and so on. So I think that this could be, for example, bad for accessibility. And when it comes to performance, this is also bad, not because of the size of your class name, but because you can be creating really nested on structures and this excessive dom size should be avoided. And you really see this if you check, for example, the performance inequality gap for the last year, where basically you come to this realization that computers or smartphones and networks are not as powerful as we tend to think.
6. Challenges with Component Exploration and Querying
As a developer, it can be challenging to find components when exploring with developer tools. The lack of a close mapping between component names and class names makes it difficult to narrow down the effective components. This is especially problematic for team members who are not familiar with every component in the code base. Additionally, querying strings can lead to multiple results that are not meaningful, making it hard to determine where a module starts and ends.
Another thing that I have to raise as a developer is the experience you get when exploring things with developer tools. So first, I find it really hard to find components. So that's something you usually do when you're troubleshooting a large code base. It is important that you can narrow down the effective components. And that's easier if you have a close mapping between the component's name and its class names. But here, with Atomics and Assassin's Jungle, we only have two options. Either we know very well that part of the app or we are querying strings in the tools. But the thing is, not everyone on the team is familiar with every component in the code base. So think of new hires, for example, or large enterprise scale monorepos. And the second thing is strings can actually be reused across multiple components. So if you're just querying strings, you might have multiple results that are not meaningful. And I guess that the results here, it is hard to determine where a module starts and where it ends. And this is nothing new and doesn't even have to do a tail end. Adam Silver pointed that in his piece called the Problem with Atomic CSS back at the time.
7. Tweaking CSS and On-Demand Tech Generation
Tweaking CSS can be challenging, especially when trying to experiment with components. Tailwind CSS offers an ecosystem with various features, including on-demand tech generation. By generating only the CSS that is used, developers can avoid generating unnecessary code and improve performance. This feature is available in Tailwind Version 2 and is the default in Version 3, along with other tools like GNOME CSS.
Another thing is it's also hard to tweak CSS. So it's common for us to do some small changes and small fixes in dev tools to figure out how it will look after we change the code. But the thing here is, if we want to experiment with our components, we have to give them a new class name and use that as a selector so that we can tweak that selector. Or we can use inline styles, but then probably we won't see changes reflected throughout the UI. And that could be the thing we're looking for.
We're also here to discuss what can be the good or maybe the good way of doing things, and I really liked this tweet by Steven where he points that Tailwind isn't a tool, is a whole ecosystem. And Tailwind has a lot of crazy things, and I think they can be even hassle if we do some things. And the first one is leveraging on-demand tech generation. So if you're thinking about generating atomic classes, if you were to, for example, implement them with some CSS preprocessor, you would, for example, loop through classes, and then you would end up with, for example, multiple classes for margins and things like that. So that's what on-demand generation looks. You end up with some CSS that is wasted because you have to purge it afterwards, and you might not generate all the CSS you actually need. So the first recommendation is to shift from this into this, where you only generate the CSS you are using. And if you're using Tailwind, this is available since Tailwind Version 2, and it's even the default for Version 3. They're just in Time Engine, and other tools also offer that, like GNOME CSS.
8. Applying Atomic CSS and Type Safety
Be aware of Apply and the potential drawbacks. It's important to understand the benefits that Atomic CSS provides, such as split class names, time-saving, and a single source of truth. Developers should be cautious not to use functional classes to undo component rules. Type safety is now addressed with tools like Tailwind CSS ClassNames and TypeWind, which offer zero runtime, type safety, and auto-completion. These tools can be used in CI to catch errors at compile time.
The second thing is be aware of Apply. So even Adam has written before about the bad sides of using Apply. If you're not familiar with Apply, I'm talking this kind of usage. And the thing here which I find tricky is you're missing the benefits you gain over the traditional approaches to CSS. So you're missing the splitness of the class name, you're missing the time you save by not having to write your own CSS, and you're missing the single source of truth that Atomic CSS gave you. And it's common for you to see a lot of posts out there mentioning how to use the best of both words, and it's common to see those snippets of code where you have a hybrid approach. The point is, I think there is a bad potential here because developers could be using functional classes as an escape hatch to undo the rules that were defined for Components. So if you're using Apply or something similar, you'd have to be really strict so that you don't have concerns being intersected by one another. The other thing is type safety, and I feel like for some communities, for a lot of time Tailwind's not having some way of checking times or working with TypeScript was a struggle in adoption. But nowadays you have amazing tools like Tailwind CSS ClassNames or more recently TypeWind. So these are zero run times tools that bring type safety and auto-completion and all that. And because you can catch errors at compile time, you can even use that in your CI.
9. Style Sheet Composition and Atomic CSS and JS
Style Sheet Composition is important for building a UI kit. Tailwind merge and ClassNames utility can resolve conflicts. The classVariancyQuantity library (CVA) handles complex cases. Leverage tooling for accessibility with libraries like Redix. Use tools to simulate impairments and catch issues with XLanguage. Atomic CSS and JS allow monolithic styling. Libraries like Stitches, Styletron, and Vell offer benefits like code splitting and CSS rule management.
Another topic is Style Sheet Composition, which is really important, especially if you're building a UI kit. So if you're not using Tailwind merge, it's an amazing tool that you can combine with ClassNames utility to build your own ClassName function that's going to resolve for you a lot of conflicts. And then you can use in your components by just passing ClassNames And you can use that in components that get ClassName as a prop.
If you have more complex cases of stylesheet composition, you can use the classVariancyQuantity library, CVA. With CVA, you can basically define variants in the case of the bottom. For example, we could be defining the size of a button, the roundness, et cetera. You can define the full variants, and then you can pass these as props to your components in the case of our button components, and CVA is going to resolve that.
As you can see, it's a lot about tooling. So, do leverage the amazing tooling we have out there, like gradient plugins or the ESLinux plugins, which by the way addresses some of the problems I mentioned when it comes to being hard for the human eyes to scan. And do leverage tooling for accessibility as well. So, there are amazing libraries like Redix and there are amazing cases of combining Tailwind with Redix to build accessible components. So, check these out. And also bringing accessibility tasks into your process, like your build process with tools like XCore, JSX alley, Lighthouse, etc. And even in your CI so you can get errors is you come up with pull requests, that's amazing.
And on a daily basis as a developer, use tools to simulate kinds of impairments. And for example, if you are concerned that the cultural point I mentioned of having tag soups and etc. could be a problem with accessibility, you can use XLanguage to catch them in a pull request.
And the last topic under this section is probably atomic CSS and JS. So there's this great post called the shorthand longhand problem in atomic CSS that goes through some problems of CSS and JS and atomic CSS. And the offer makes a really good point on atomic CSS and JS. They're saying, it allows us to write our styles in a familiar monolithic way, but getting atomic CSS out and this whole thing isn't new on Twitter for example, has been doing it for years in React Native without and Facebook has their own take on that which is open source yet. And there are a bunch of libraries out there like Stitches, Styletron, Compile CSS and JS, Vell, and others. And you do have benefits. So for example, you no longer have the need for a specific class name convention if you just like the conventions of Tailwind for example, you have common and one-off styles treated the same way. You can also get extraction of critical CSS and code splitting and even address some other problems like issues like CSS rules in searching order.
I know we talked about a lot of things. So before we go, just some closing thoughts. I understand that it's really easy to get into this thing of this is just internet drama and your users don't care which tools you're using. And especially because we're having like a lot of discussions on the internet on this topic. But another thing we're having are some posts or for example, people who went with one methodology and had to change that into something else.
10. Considerations for Tailwind CSS
And that's the case for people switching from Tailwind to something else. Picking the right tool can make a huge difference. Tailwind is a great fit for component-driven development. Tailwind may not be suitable for all projects. Tailwind may not play well with Web Components. There's an emotional attachment to technology choices, which can be problematic. If you have any questions, I'm available online. Thanks for your time.
And that's the case for people switching from Tailwinds into something else. And you even have tools to convert Tailwinds to try to convert Tailwinds back to semantic classes. So in the end, you're right is just tool but picking the right tool can make a huge difference. And that can be Tailwinds, it can be some CSS and JS approach, semantic CSS, CSS modules, whatever.
The other thing is we see that the solution to the problem might only change the problem you have. So it's a lot about thinking in terms of trade-offs, trade-offs and trade-offs. Even though I advised you to be aware of apply, it's important for me to highlight that utility classes can actually coexist with other approach. And the sweet spot for you might be some hybrid of both conventions and Bootstraps is an example. So in their latest releases or in recent releases, they introduced a lot of utility classes while keeping the traditional components. So many other popular frameworks have been doing the same.
The other thing is I think that Tailwind suffers a lot from people thinking it applies to all the projects. And I, for example, think that Tailwind is a great fit if you're doing component driven development, like React, Vue, Spelter, whatever. If you're not, then probably Tailwind is not the thing for you. And another thing is we've had many complaints of people saying that Tailwind doesn't play well with Web Components because of shadowed on, et cetera. But the thing is, they are encapsulated and Globe CSS won't have any effects. So that's the whole point of encapsulation. So maybe there is not a lot of sense in using lids, stencil and other Web Components alternatives with Tailwind, even though it's feasible. I really love this quote by Guillermo where he kinda compares the momentum Tailwind has with JSX and React, React Pad. And yes, JSX kinda created a similar feeling in the community back at the time because it was going against so many of the best practices we had that we would never consider that a good idea. So I think it's interesting to reflect about this. And last but not least, unfortunately, there's a lot of, there's an emotional asset to the choices that developers make. And there's even a name for that in psychology that is team identification. And that's bad because you're just getting attached to technology and choosing sites when it probably shouldn't be like that. So that's all I had for today. If you have any questions, I'm available online. And again, the links are there. Thanks for your time.