These two usecases is what makes Tailwind so useful and accessible for drafting stuff up, but also bear the pitfalls of tech debt to actually maintain and iterate the design system of complex ui systems
What irks me is that so many people seem to be utterly unaware of this intent, and rather regard Tailwind as some sort of "better css", on the basis of not knowing much about CSS
But a truly good and flexible design system needs all of it in the end:
- a top layer config that allows definition and maintenance of design tokens
- an intermediate layer of abstracted presets to ensure portability and maintainability of design system specific patterns
- a lib-level layer of utilities to avoid having to force one-off declarations like the layout of a specific view into either of the other abstractions (Ex: "search results as 3 column grid on desktop" is not a design system concern, it's neither a "style", nor relevant to any other components or visual styles)
When it comes to keeping styles & markup together, this has always been possible with simply using `<style>` tags in your templates or components. The performance impact is absolutely negligible IRL, and any leakage or selector naming concerns have also become a none-issue with `@scope`
Don't get me wrong, Tailwind has the RIGHT IDEA! It solves to top level layer of managing the design system, which also is the main reason for it's success imho
The big issue is that it is constantly being used the wrong way, @apply which would allow for proper abstraction of component styles is largely being ignored, and thus instead of forcing utility into superfluous class bloat like it's common in framework-less vanilla CSS integrations, most of it's users rather force the intermediate abstraction into the utility-only layer, producing unmaintainable garbage markup and utterly illegible and repetitive style declarations.
And to get back to my original point: This comes imho from the widely prevalent fallacy that Tailwind ought to replace the "traditional way" of writing styles, and failure to understand that it is meant to merely extend on it & utility classes should be employed in moderation and for specific purposes in design-systemized UIs
Tailwind is really rotten for complete design systems, (I'm not just talking about an app's theme like colors and fonts)
It's basically putting a bunch of CSS into "vars" or macros.
But the way this systemisation is integrated throws away most of the advantages. What good is having "p-4" abstracting "8px", when you then repetitively hardcode the alias all over your markup instead of defining individual, reusable and easily maintainable classes?
This is also makes "zombie CSS" a thing of the past, since the resultant CSS file that tailwind generates is based solely on what is in use.
Write a class & map it to the relevant component or DOM node in a template, so you only have a single source of truth to maintain the styles of that component.
Writing the explicit style declarations directly to the DOM nodes themselves is precisely what _prevents_ portability and reusability.
The perf issue of "zombie css" is seldomly an issue in my experience: if it really impacts your load time or perf, you can still easily serve subsetted stylesheets at build or request time
If the perf impact is negligible, you might in turn profit from serving all the css at first, since it will be cached and accelerate subsequent request or rehydration
Removing unused CSS is mainly a concern for _inline_ styles, since the bloat of the initial HTML might impact FCP and repaints/reprocessing once additional stylesheet are loaded.
... which ironically also means that adding truckloads of utility classes to every DOM node might introduce quite some initial load & paint performance implications aswell, depending on your app.
It might even be interesting to benchmark if a stylesheet with a bunch of "zombie css" really has more perf impact than serving a minmaxed css file, but requiring dozens of classes on every other DOM node to be processed for painting
it's not a file size game in the end, the amount of selector statements & DOM nodes they might apply to has much larger paint perf impact than a few kb of unused gzipped text
Just FYI that Bootstrap 5+ has really good utility classes: https://getbootstrap.com/docs/5.3/utilities/background/
This plus their component classes makes Bootstrap a lot more practical for most needs imho, and without needing some build step.
Just do vanilla `<style> @scope { /* your component styles without any naming bloat ... */ } </style>` nested in the markup/jsx, and optionally leave merging any of that for perf optimisation to some rudimentary build tooling or hydration
But also: I do see the QoL benefits of colocating styles & markup, but it has drawbacks aswell: it prevents you from being able to reuse design systems across different applications/codebases, and increases maintenance complexity and tech debt as soon as you introduce utility classes or any styling rules that aren't hard-tied to a singular component. Both issues scale painfully fast with the complexity of your App/UI, and impact Tailwind-based UIs even more in my experience.
If you're doing relatively straightforward things, tailwind is super easy to work with and that's why a lot of people prefer it today. Having it inline makes it easy to digest (both for humans and LLMs) until things get complicated.
I see this similar to using javascript in your project. When you start fresh on a new project, typescript can be seen as a bottleneck, slowing you down. As project evolves your needs also evolve and other solutions might be a better choice.
CSS modules provide a great alternative if one is willing to write CSS in separate files. Otherwise, Tailwind has provided a smoother experience than CSS-in-JS for me.
1. Allow styling to co-exist with your DOM layout rather than separate files
2. Provide a ready-to-use style system built atop utility classes
Emphasis on "utility classes". This is what makes Tailwind different from e.g. Bootstrap.
I don't think the point is to avoid writing CSS. The point is to write CSS in a way that allows it to be co-located with DOM layout, so that UI components can become self-contained pieces of code.