Hacker News new | past | comments | ask | show | jobs | submit login
Signals for Tailwind CSS (styling based on ancestor state via style queries) (github.com/brandonmcconnell)
37 points by throwaway888abc 5 months ago | hide | past | favorite | 55 comments



Tailwind is both great and terrible. The trick is to take just the good parts and not let it poison the rest. Let it make easy things easier, but don't let it take over and make the hard things impossibly difficult.

Here's how to do it: use Tailwind only for layout (margin, padding, flex) and brand consistency properties like text size, line height, colors etc. Beyond that, anything that requires changing presentation based on state, embrace CSS. It was meant for this, and remains the best solution. Invest in learning CSS because it will still be here in 20 years.


Further down the thread I've left some lengthy comments arguing for exactly this.

There are two concerns in defining and maintaining design systems that are (so far) hard or impossible to manage well with vanilla CSS & decoupled styles:

1. Design Token & "swatching" to make integration accessible and maintainable for non-designers 2. Declaration of anything that is neither a concern of this core design system, nor part of explicitly scoped component-styles

Tailwind patches both, and this is the foundation of it's success -- the issue causing any App that relies purely on Tailwind to end up FUBAR beyond a certain scale/complexity, is that the bulk of Tailwind users seem to misappropriate those tools (specially the utility shorthands) to declare ALL the styles.

It's not completely on them either, since Tailwind needs to solve a conundrum: which utilities are meant to be utilities?

Drawing a line while retaining universal applicability is impossible, so Tailwind ended up just wrapping all of the available rules into utilities ... which then requires to extend the syntax to pass design tokens to any of those.

The result is a Framework that originated in trying to provide those 2 missing links, but ended up looking like a complete replacement of CSS.

And since these are precisely the challenges that used to be blockers for anyone whose primary scope is NOT the styling/design layer, and who therefore can't be expected to concern themselves with design system theory or good knowledge of CSS, it quickly ended up in the current ubiquity of UIs getting implemented using a misguided kind of "html style attribute only" paradigm in their entirety -- despite Tailwind originally following a much more nuanced and sensible idea.


This is refreshing to hear. The DSL provided by this plugin feels like an entire new language to learn. My experience with Tailwind has been positive but I'm not sure I want to learn new ways of doing the same things just to stay within a pure Tailwind framework.


I’m a strong advocate for Tailwind, and I believe that this approach is truly effective. In my experience, this subset of Tailwind has been sufficient for every project I've worked on that used it. Moreover, I've observed that it often converts skeptics into supporters.


Curious about your expectations: Do you rely on utility classes only in your workflow, or do you split reusable patterns into "original CSS"-like @apply blocks?

And how would you categorise the complexity of the Apps/UIs you build like this?

I'm very interested in the specifics of successful applications of Tailwind that the maintainers keep deeming a good solution, since CSS as a whole is one of my main scopes and I've had quite some projects that where basically some variation of writing custom brand-lib frameworks for teams who embraced Tailwind at first, but in time ended up hard locked when needing to refactor or iterate their UI


Great question. We rarely rely on shared utility classes these days. Historically we'd end up going the @apply route, but lately we just add a new style to the root-level css file and call it a day. We may prefix it with "@layer" so that it gets tree-shaken, but that's about it.

I've found that in component-driven UI, the need for these kinds of utility classes becomes less and less necessary. The utility CSS may be defined in the component, and the component is ultimately what gets reused.

Could you elaborate more on the reason for your interest? I've used Tailwind to implement several well-defined design languages, and always had great success. In fact, compared to other design systems, Material for example, I found it to be far simpler to manage over time.


Tailwind is CSS though? Like, it much more strongly maps to individual styles than most other popular style frameworks and systems.


This is the core fallacy though, imho: css ITSELF is already the tool for "mapping individual styles" to DOM elements.

Oldschool "CSS Frameworks" are libraries intended to bootstrap UI creation by providing abstractions of those mappings for a set of universally required UI patterns.

Tailwind pioneered the idea of "functional css" as a means to fix the "one-off classes" style bloat for anything that is NOT a specific component pattern (think "this specific archive view should be presented as a 3 column grid")

But when you end up aliasing all of CSS with such utilities, and then writing your styles to markup atts directly ... you aren't using a "framework" anymore, you simply "wrapped" most of CSS itself in a bunch of rainbow tables and littered it all over your markup, resulting in a highly illegible and unstructured style system


Can you elaborate on how to do that without duplicating style configurations?


This is really fun. SolidJS has the `classList` attribute which I use a lot in scenarios where this would work. I think a plugin like this would also be preferable so as to avoid the extra JS state code which really always feels like a cheap hack.


  hover:[&>div]:bg-green-800 peer-checked:[&>div]:bg-green-800
Anything to avoid writing CSS


Well, yes, that is more or less the idea of Tailwind right?


I think OP is pointing to the irony of using an increasingly verbose and overly complex wrapper API that makes it utterly painful to maintain styles "just" to avoid learning css, which is regarded by many developers as confusing or hard to learn, but is actually much more accessible, legible and maintainable than what Tailwind had become by now.

I might be reading a lot into this remark and mostly relying my own opinions here though.

But throughout the years I've been seeing how CSS is universally met with insane prejudice and expectation of it being "too complicated" or painful to learn, and Tailwind being praised as the solution while actually being much more verbose and obfuscated, and time after time leading to irrecoverable tech debt, utterly illegible markup, and complete dead ends whenever style needs any kind of refactoring.

Now that CSS is maturing and getting tons and tons of new features, Tailwind lags behind and keeps introducing more weird workarounds and hacks to be able to support all of it. The line in OPs comment is an example of it literally coming down to writing mutilated CSS selector statements directly to html attributes just to be able to do some of the stuff `:has()` enables, and creating horribly illegible markup and non-reusable styles in the process ...

Imho the original issue leading to "stylesheets are a pain" was never really CSS itself (at least since module 4 & custom props), but rather the top level management of tokens in a design system to not have to memorize colors and sizes etc -- Tailwind is simply collaterally fixing the right problem with the worst approach thinkable, but keeps being hailed as a holy grail by people who lack understanding of this.


Perhaps the point of Tailwind isn't to obviate the need to learn CSS. Which seems obviously true to me, given that if you don't know CSS you can't really use Tailwind very effectively.

There's definitely a 0.1% long tail that's tricky with Tailwind (like this), but 99.9% of app styling is so ludicrously fast in Tailwind as compared to writing raw CSS.


It definitely isn't, my point is that it seems to be largely is misunderstood as this being it's main purpose.

Tailwind is unquestionably accelerating drafting up a custom UI, and also makes this much more approachable for webdevs unaccustomed to CSS

The pitfall of declaring all your styles as strings of shorthands in html atts also might never hit you if your UI doesn't get too complex and/or never needs refactoring - but it hits hard if your codebase had grown to a certain scale over the years, and you suddenly need to change or refactor larger parts of the UI


> There's definitely a 0.1% long tail that's tricky with Tailwind (like this)

That's the first example on the README.

(Though perhaps the author of this project did a bad job as choosing a representative example of Tailwind code.)


Right, because it's a usecase that (allegedly) warrants a totally separate package/plugin. Not much CSS is dependent on reading ancestor state.


Reminds me of a common complaint about ORMs. The easy stuff is easy and the hard stuff is insanely hard.

This recent post convinced me to just use CSS again: https://dev.37signals.com/modern-css-patterns-and-techniques...


I disagree with the main motivations and benefit. For me it’s always been about being able to handle hover states etc without having to leave the local context where I’m working. I don’t think anyone can really write tailwind without understanding css.


Adobe Brackets had an interesting solution to the context switching problem. There was a keybind, I think CTRL+E, that would let you edit all the styles that apply to the current element in a kind of inline sub-editor. It was quite neat and I haven't seen it done in any other editor.


You didn't have to leave the local context if you use very local stylesheets. Scoped CSS like shadow DOM, plus CSS text right with your component, helps a lot with this.


Style attribute lacks a selector (“color:red” vs. “.comp {color:red}”), forbidding the most useful use case. Shadow dom is an inspection nightmare used only by a few progressive web purists. Style.css is an untyped incoherent spaghetti. There’s no really good place for an element style in current implementation.


op is not talking about style atts, but referring to nesting inline `<style> @scope { ... }</style>` to declare vanilla css in the scope & context of specific DOM nodes


No, in taking about the native scoping you get with shadow DOM, which is awesome and not an "inspection nightmare".


Isn’t it a “limited availability” feature? Why would they use it?


Yeah, but `@scope` is only if you don't want to support Firefox.


if the styles only concern the context you work on, this could easily be done with vanilla scoped inline styles

If those declarations are relevant to multiple components within your codebase, then you'll still need to leave the context when your styles are inlined and therefore declared in multiple places


No. That’s the point of tailwind. You can include things like hover states. Inline css hits a roadblock there and you’re forced to go out to a style tag. Then you have to start naming things. Then you risk conflicts.

That’s the problem tailwind solves.


CSS took 20 years to begrudgingly incorporate the most useful features of SASS, and these are still not wide spread or (like nesting) have become available barely a year ago.

CSS is verbose, repetitive, error-prone, and requires insane workarounds to keep it manageable.

There's a reason why SASS has been nearly a de-facto industry standard, and why abominations like BEM exist.


SASS and LESS have their place, but they are tooling, not replacements

The time it took for CSS to progress like this although is on Browser vendors, not on the language - lots of recent features that seem utterly late to the game have existed for years and years, but had to be deemed "unsafe" to use because of Safari, Firefox and other browsers that lag far behind the standard.

And honestly, the critique you throw at CSS is all the more true for Tailwind actually, and imho only stands the test for _badly written_ code, which holds true for any language in any domain.

And regarding why BEM exists: it's just a flavour of naming convention for your "API", nowhere mandated by CSS itself. Its main purpose is to optimise paint performance and keeping your component naming lean and transparent. You may very well simply write expressive and specific queries instead of defining naming conventions for your components.

But I'm unsure if we talk about the same things in the end, cause none of those points get any better with Tailwind, rather the opposite.


> because of Safari, Firefox and other browsers that lag far behind the standard.

I could've guessed there would be a blame game.

No, neither Safari nor Firefox lag in web standards. Moreover, it was Firefox (IIRC) which figured out how to do nesting safely.

However, even beyond nesting there multiple other things that lagged in browsers. CSS Scope was completely derailed by Chrome's push for Web Components, and had to be solved by other tools.

Nesting, mixins and a lot of other tools were long seen by all browser vendors as "unnecessary and better solved by tools". So yes, SASS isn't a replacement but there are reasons why it existed and was a nearly de-facto standard in tooling for so long. And that reason isn't "CSS is so great".

> And regarding why BEM exists: it's just a flavour of naming convention for your "API", nowhere mandated by CSS itself. Its main purpose is to optimise paint performance

No, it's main purpose is to try and close the gap in CSS: CSS is/was absolutely horrendously unsuitable for any type of componentized code. And BEM was an attempt to solve that at the process/naming level.

No, BEM isn't mandated by CSS. However, it exists because CSS had literally no tools to support the use cases BEM was aimed at.

> But I'm unsure if we talk about the same things in the end, cause none of those points get any better with Tailwind

I didn't even talk about Tailwind. I was directly addressing your claims about CSS.

CSS was never great.


Thanks! This is helpful.


I've understood the point of Tailwind to be two things:

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.


If anything this puts a slightly higher burden on the dev because you're still always responsible for the underlying CSS, now you just have to be aware of how the utility classes map to CSS.


I think this is precisely what I am ranting about.

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


> 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

Tailwind is really rotten for complete design systems, (I'm not just talking about an app's theme like colors and fonts)


this is exactly the point, using it for ALL the styles was always a horrible idea -- and never it's intention (i think)


And when the CSS is "inline" via classes vs raw style then you get consistent padding (p-4) vs some things that vary and are hard to update theme-wide (padding: 8px).

It's basically putting a bunch of CSS into "vars" or macros.


Exactly my point: it provides an interface to manage design tokens, instead of having to think about those constantly and litter your code with magic values everywhere.

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?


In order to get value out of Tailwind, you need to be using a templating language that lets you create reusable components. So in this way, you are not repeating the 7 classes each time you need a card, but just using the component.

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.


isn't this exactly what CSS is meant to solve originally?

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


> Emphasis on "utility classes". This is what makes Tailwind different from e.g. Bootstrap.

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.


The last paragraph sounds like CSS-in-JS


Tbh I never really understood the reason for the existence of css-in-js either.

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.


For one thing, Firefox.


This is what most people want + some utility functions & good default variables (for colors etc).

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-in-JS solutions like Emotion provide something close to that! But they come with some drawbacks (e.g. needs careful setup for server-side generation, spends a few CPU cycles translating to CSS, breaks memoization unless one uses deep object comparison or stores the style data separately from the component).

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.


Not really. The example is worse than CSS in many ways and the idea of tailwind wasn't to be worse than CSS.


I didn't know about the @container queries, they look interesting.

Having said that, I'm not a huge fan of pushing more state logic to CSS. I understand the benefits (from the github page) but pushing more text into the style string without thinking about tooling (how to debug etc) is dangerous.


Write-only code is so hot right now


It is a hype phase. Everybody adds tailwind, later everybody removes tailwind. We need to find ways to be busy anyway.


Tailwind has _the_ highest retention of any CSS framework ~75% YOY according to StateOfCSS surveys (2019-2024)


Yeah, for like 2 years? That's the hype phase.

(you are talking BS, 2024 there is no data about it, 2019 there isn't also. if the stats you brought is true it would be at maximum from 2021-2023)


style queries

As expected, they didn’t stop with :has and slowly turn it into a subturing abomination.


NSFW but I just want to point out that using form validity semantics we are not constrained to telegraphing state within local element hierarchy/adjacency but can in fact toggle a binary, selectable CSS state from anywhere on a page.

https://codepen.io/inopinatus/pen/vYxrOeR




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: