Hacker News new | ask | show | jobs
by lenkite 328 days ago
If you go even minimally outside the beaten path, the tailwind CSS declarations can mutate into a frankenstein monster that makes regular CSS look like a friendly, cute koala

Example: https://www.nikolailehbr.ink/blog/realistic-button-design-cs... shows an "old fashioned", 90's are back-in-vogue, 3d button.

Tailwind CSS for it becomes

    <button
      class="relative cursor-pointer overflow-hidden rounded-md border
        border-neutral-950 bg-neutral-900 px-3 py-1.5 text-neutral-100
        shadow-md inset-shadow-2xs inset-shadow-neutral-600 transition-all
        before:absolute before:inset-0 before:bg-linear-to-b
        before:from-white/20 before:to-transparent hover:bg-neutral-800
        active:bg-neutral-950 active:shadow-none
        active:inset-shadow-neutral-800">
      After
    </button>
I got eye-strain and headaches after taking over maintenance of a tailwind based website where the original developer had left the team. The class declarations are so huge with 15-30 class names in one line that you always forget where you are. Incidentally, also the top-voted discussion: https://github.com/tailwindlabs/tailwindcss/discussions/7763
8 comments

Is that more or less verbose than

  button {
    position: relative;
    cursor: pointer;
    overflow: hidden;
    border-radius: 0.375rem;
    border: 1px solid #0a0a0a;
    background-color: #171717;
    padding: 6px 12px;
    color: #f5f5f5;
    box-shadow:
      inset 0 1px #525252,
      0 4px 6px -1px rgb(0 0 0 / 0.1),
      0 2px 4px -2px rgb(0 0 0 / 0.1);
    transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
  }
  button::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0.2), transparent);
  }
  button:hover {
    background-color: #262626;
  }
  button:active {
    background-color: #0a0a0a;
    box-shadow:
      inset 0 1px #262626,
      0 0 #0000;
  }
Sure, separating the code into a CSS file means it's not in your HTML but that doesn't mean it doesn't exist. Similarly you can move the button out to a component and reference that if you want to hide the code.

I find it easier to locate things in the HTML broken into components because the style is co located with the object being styled. I find it much more difficult to navigate through CSS and find out what classes are doing what exactly. Especially when a project grows over a long period of time and different people write CSS differently (and throw a few !important's in to make sure they can meet a deadline).

Each to their own though.

It's not quite apples-to-apples because the Tailwind code is using design tokens and the CSS is not. You should (for example) replace the long `box-shadow` value with `var(--shadow-md)`.

Anyway, to me this question is kind of like asking "Is it easier to break your writing into paragraphs, or write everything in one long block?"

Like, what would you think if I formatted my comment like this?

> It's not quite apples-to-apples because the Tailwind code is using design tokens and the CSS is not. You should (for example) replace the long `box-shadow` value with `var(--shadow-md)`. Anyway, to me this question is kind of like asking "Is it easier to break your writing into paragraphs, or write everything in one long block?" Like, what would you think if I formatted my comment like this?

Most people use Tailwind with components, and so when you talk about breaking things out into paragraphs, that would be the equivalent.

Sure that html looks a bit messy, but once you write it once you're never looking at it again. In your view files you're just writing.

  <Button>Click Me!</Button>
Or perhaps injecting variables etc.

  <Button color="red" style="outline">Click Me!</Button>
I'm not really trying to argue that Tailwind is better or worse, I'm just saying it's a valid way to do things and there's nothing inherently wrong or flawed with it.
This isn't something a web developer should be doing in 2025. The styling should be generated by the software used to design the UI.

Why aren't the companies building design tools solving this?

Adobe has been trying to build this product for 30 years and still haven't figured it out. Figma is trying and the best you can get is partially usable copy and paste CSS.

This is a "why don't they just make self-driving cars" question. The answer is that there are too many edge cases.

I don’t think building autonomous cars is on the same level as writing CSS.

We're entering a phase where natural language becomes the main interface for html and css development. Companies like Vercel, Wix, and Framer are integrating AI to turn design prompts into working UI components.

This is only the beginning. Within 2 to 3 years, domain-specific language models, trained specifically on frontend code, are expected to become common.

Regarding Adobe, they never really understood developers.

I'll believe it when I see it, and so far what I'm seeing is "a llm lied to me and deleted my codebase"
I think you should find a new job.
XML developer
There is also additional risk by overusing CSS's built-in selectors and inheritance, whereas there's zero risk with Tailwind.
To each their own, the Tailwind version is more readable for me.
1. highlighting

2. syntax checking

3. language servers.

100% this. People have all of these highfalutin arguments about tailwind being the wrong abstraction/non idiomatic etc. But, as someone who writes a lot of CSS—not having to jump back and forth between a CSS file and a HTML file is a game changer. CSS files are just another thing to maintain, and a painful one at that, since CSS is mostly organized relatively arbitrarily. This is why Tailwind won; reducing the surface that developers have to maintain is a big deal.

There are simply so many decisions when choosing how to implement CSS in a project—where should we put the files, should they be component based or global, what preprocessor if any, etc etc. With Tailwind, you don't have to worry about any of that.

There's a lot of people, including oftentimes the tailwind dev(s?) themself, who view Tailwind as a replacement for both CSS and Components.

I think this is an L take. Tailwind is a solid replacement for 99% of the CSS you'll have to write. Its not a replacement for components. It makes components better.

Just wrap that monstrosity in a component; at that point I'm not sure why it matters. Have you ever looked at the component definition for an @mui/material button? Its a cthuluian insanity. The people who maintain that library likely need therapy every week just to remain functional humans. And, I thank them deeply for their service, because I so rarely have to look at those innards; they just work from the outside.

Some judicious use of @apply would go a long way. But for some reason a few years ago they were actively deprecating its use, favoring the above insanity instead. Now @apply seems to be back in good graces, but I guess not everyone got the updated memo.
@apply in CSS modules to compose the above sort of thing into something more semantic and readable without creating a bunch of global artifacts is definitely the way to go, IMO.
I see buttons used as an example a lot but buttons are one of the most style heavy elements on many websites and are only a single element so it's not a good comparison imo.

For a lot of pages, you're often only adding maybe 1 to 5 styles to each element to change some flexbox settings, add some margin, set a background color, or change a font. With the traditional CSS approach, you're forced to come up with class names for each element and put the styling into a separate file, which makes iterating on style and layout really laborious compared to Tailwind.

That's a much bigger benefit to focus on rather than looking at how (single element) buttons are styled. It's not like the CSS for styled buttons is less verbose or even pretty either.

The power of CSS is that it can capture semantics. Tailwind gives up on all of that.
I’m a bit lost on this point - can you give an example of CSS capturing semantics?
What? Since when did CSS become about capturing semantics? That's what the Html elements are for. CSS is about the presentation.
Last days I have spent with Tailwind. But honestly, even if I know all abbreviations now :), and it it eye-bleeding syntax, is there something better in the wild? I know that I could use vanilla CSS, but… how many times do you have enough time to build your own CMS when you can install WP… life is short. Most of us are not building a sustainable system that will be here in next 20 years. Life is short. Deliver fast.
I can't believe people find even half of this acceptable, do they never use a browser inspector in their work?
If you write enough CSS for a site... eventually you end up building utility classes anyway.

Now, you re-invented Tailwind... but in your own proprietary way that nobody else understands.

Tailwind takes the inverse approach. The example posted by the parent above is completely unambiguous - what you see is what you get, and it's done the same way everywhere Tailwind is used. You can read the component's styles and understand what it should look like without having to do a bunch of look-ups, or alter classes which might impact other areas.

So now instead of memorizing a bunch of custom classes like `pad-left-20` (because you didn't discover the `leftp-20` your colleague added 8 months ago)... you now have a standard way.

When I write CSS, I think in layers:

(1) General CSS, global effect, like basic font size

(2) semantic components on my pages, like a special kind of list or something, that I give a well thought out name. These CSS definitions are always scoped, obviously to ".my-class-name something" selectors. They cannot affect anything that is not inside a semantic component. It is very simple.

(3) layouts/containers, that contain the components, like some flexbox or grid or something that behaves a certain way, which I also give a meaningful name

(4) A theme, a CSS file that contains CSS variables, which have values used in the other layers. Often when I need to change something, I only need to change my theme.

I do not understand, why web developers en mass are unable to cooperate with each other to develop their semantic units for CSS and then stick to those, instead of sprinkling stuff everywhere and using !important to make shoddy work. If they disagree about the semantic units, then that is the same problem as we have in any other software development arguing how to box things. Furthermore, the semantic units should be part of the design language of the business. The designer should realize "We have 3 kinds of buttons. Each has its own set of rules." and then what is easier than making 3 CSS classes? -- I am sorry, I do not understand where the difficulty is with just using CSS. Someone please, please explain to me, what is the problem in this day and age.

To me, the problem is distance. Once a project reaches a certain size, the distance between a thing like a component and where its styles live grows to be too far for mental context.

If you’re working on a page that gets variables from this file, component styles from a scoped style block, typefaces from one file, layout from another…it all becomes a bit much.

Pair that with tooling that doesn’t _seem_ to easily lend itself to discovery - with any function or method or variable in my Vue files, I can tell the LSP to Go to Definition and I’m there. But CSS classes? Maybe. Depends - is there a preprocessor? Is there an abstraction layer that makes it hard to follow? What if someone did an “oops” and overwrote a global style in a scoped block?

On smaller projects or regimented teams, keeping styles in a well-organized structure of CSS/SASS can definitely work. But once you have even a modest handful of imports, or you’re bikeshedding the discussion around how to name classes and variables, it gets unwieldy.

Anecdote time: when I came into my current workplace, our project was already a mess of sorts - Craft CMS, Vue, SASS, just for the frontend. Backend is a separate service that drives a lot of the main content (CMS side is secondary for marketing stuff). The style situation was a mix of SASS in folders for “general stuff”, “project specific stuff”, and “administrative portal stuff”. Oh and then scoped styles per component. There was no clear design language that was shared among teams - the Craft team largely did the original SASS work and painstakingly implemented the design team’s vision for everything from breakpoints to grids to colors to forms. But we weren’t really keyed in on that, and by the time I got there we were rolling up some…50? individual SASS imports plus scoped styles - yikes!

Last year we began the process of separating the various sites from each other - CMS keeps being CMS, customer frontend and admin areas are both separate projects. I made a push to adopt Tailwind and there were the expected “I don’t get it” people and i gave them the crash course on the naming conventions and using the docs. Add the Tailwind LSP and it’s really a no-brainer: we have one single CSS file, per project, that configures things like color tokens and base style overrides for eg breakpoints. Everything else is pure Tailwind.

I’m in a button, my button styles are all right there - no context switching, no hunting down disparate selectors, no worrying if someone slipped an !important into the code somewhere down the line and broke something for everyone else.

I was hugely skeptical of Tailwind some three years ago, gave it a shot, and wouldn’t go back at this point. Sure the syntax is verbose, and I embrace that over the hidden nature of your styles being defined in one (or several) places and your makeup being elsewhere.

There's a big difference between using utility classes for utilities and using utility classes for everything.
Honest question... why?

Can you tell me, without looking at the implementation, what effect `btn-atc-primary` applies when the user hovers? No... you cannot.

It's less important to have "clean" looking markup than it is to be unambiguous in intention and implementation. If you read a component that uses Tailwind classes, you know - without any doubt - what it does and what it looks like. That's pretty cool...

And here we’re on firm bike-shedding territory. Where does a utility stop being a utility and start being… a non-utility?
It's less of when it becomes a "non-utility", and more that it becomes an incomprehensible mess of classes on a div.
For me, `class”btn btn-primary btn-primary—title”` says close to nothing about the underlying CSS - I have to go look that up. But `class=“rounded p-2 flex gap-2 items-center font-semibold bg-aqua-300 text-white”` actually tells me something: “this button has rounded corners, padding of 8px on all sides, it’s got flex display with a gap of 8px, items are aligned along the center of the cross axis, font weight is 500, the background is a lighter shade of aqua and the text is white.”

What shade of aqua, exactly? I couldn’t tell you, but my IDE sticks a color block right next to any Tailwind color class. Is that rounded corner a radius of 6px or 8px? I don’t remember off the top of my head, but again my IDE will tell me with a quick hover of the “rounded” class.

To me, the first set of classes is “incomprehensible” because those names, while semantically descriptive, tell me nothing about what the classes actually apply - they describe what they’re styling, not _how_ they’re styling it.

But the long list of Tailwind classes tells me exactly what they do and how they do it, and if for some reason I need to go beyond the LSP to figure out what a class does, the Tailwind docs are miles easier to search than grepping the codebase for “.btn-primary \{“, not that I’ve run into that. In fact, the Tailwind LSP will compute selectors like “[&>div]:hover:border-2” and show you the actual generated CSS selector right in the hover window so you can see what the eventual output will be - it’s not just a dumb lookup of “m-2 is the margin 8px class”

You can just write padding-left: 20px; and not memorize any additional layer? If you really love abstraction that much you can do padding: var(--pl20);

Bonus is that you actually know how to use CSS

In your example you are creating a class for a single property which is what people are complaining about. I would create a .card class that contains the padding (and all needed styles) and probably use a --site-padding variable.
There was a time when not having css and html completely separate was considered sacrilege. And having html in javascript would have been a mortal sin.

Eons ago I was at a job where we dynamically generated css on the server (php in that project). Anyone would have said: what about separation of concerns? Now everyone says it is best practice to have components with css, html and (gasp) Javascript all in one unit, because someone, for ex. Facebook said it (React). And the truth is it is very useful, and it is another way to structure frontend code.

My point is: it is a spectrum and it can be useful to change the styling right there in the browser inspector. That works with tailwind the same as css. And with components, you can hide the repetition, although the browser will still have the repeated classes. Is this better than having css on another file with maybe some unused classes? I have done both and I can see each has pros and cons.

The thing is, they were never really separate. The source files were separated, sure, but the way 90% of the world did CSS, there were hidden abstractions relating the class definitions in the .css file and the DOM structure in the .html that weren't documented anywhere.
This Chrome extension is widely recommended when using atomic CSS:

https://chromewebstore.google.com/detail/atomic-css-devtools...

I do, and it’s great having the styles right there and hackable.
No matter what you are using, they are always there, editable/hackable.

However I would argue that it makes it slightly harder to make any change. You no longer have a single class with all the styles that are affecting that element.

You can change the classes in the harder to read html, or override the single element. There is no longer the possibility to, for example, affect all .primary-btns.

> You can change the classes in the harder to read html, or override the single element. There is no longer the possibility to, for example, affect all .primary-btns.

Sure there is, the only thing is that instead of having 20 <button class=“primary”> tags, you have a <ButtonPrimary> component. Change the component once, and all instances get the update.

This applies regardless of Tailwind. But if you’re repeating classes on the same kind of element over and over, then the question is less about how you’re styling it and more about how you’re organizing things.

To be fair, this is the case with any styling approach; if the style is applied to the element, it's there for you to toy with in the inspector.
How can styles ever not be hackable? Inline styles always take precedence. What can ever stop you from slapping in more inline styles?
Huh? You have the styles right there and hackable regardless of your tooling. That's what browser inspectors do.
I used to be against Tailwind. Now that I've used long enough I can tell you that code is perfectly fine. I can read it and easily modify it. You can comment it out if you care

      <button
        class={cn(
          // layout & positioning
          "relative cursor-pointer overflow-hidden rounded-md",

          // border & background
          "border border-neutral-950 bg-neutral-900",

          // padding & text color
          "px-3 py-1.5 text-neutral-100",

          // shadows
          "shadow-md inset-shadow-2xs inset-shadow-neutral-600",

          // transition
          "transition-all",

          // background gradient positioning
          "before:absolute before:inset-0",

          // background gradient
          "bg-linear-to-b from-white/20 to-transparent",

          // hover 
          "hover:bg-neutral-800"

          // active
          "active:bg-neutral-950 active:shadow-none active:inset-shadow-neutral-800"

          // disabled
          "disabled:opacity-50 disabled:cursor-not-allowed"
        )}
      >
        After
      </button>
Reading though this, I really have to wonder how this is better than

    <button style="css blob">After</button>
Why make a bunch of css classes when it looks to me like the "style" will be roughly the same amount of code and readability.
I am a convert, once you get the hang of the configurability it's very good (Tailwind 4). You don't have to go to separate files which is more streamlined, esp when doing in React.

I like to format in logical chunks per line, for example here's a pretty complex grid setup that is easy to adjust in the template:

    ----
    <section className="
      w-full
      px-8 py-16
      md:px-16
    ">
      <div className="
        grid
        gap-2
        auto-rows-[minmax(0px,1fr)]
        grid-cols-[repeat(1,minmax(4rem,1fr))]
        grid-rows-[repeat(1,minmax(4rem,1fr))]
        md:grid-cols-[repeat(6,minmax(6rem,1fr))]
        md:grid-rows-[repeat(3,minmax(6rem,1fr))]
        xl:grid-cols-[repeat(8,minmax(6rem,1fr))]
        xl:grid-rows-[repeat(2,minmax(2rem,1fr))]
        justify-center
        ">
    ----
You’re correct in the sense that they’re both big noisy blobs that dilute semantic/functional signals.

You’re neglecting the design system work that goes into tailwind classes. I don’t particularly like its choices (and generally consider tailwind to be a minimal local maximum for a set of tradeoffs deserving of faint praise), but they’re something, and in an engineering culture that won’t adopt a better mindset or better tooling, it’s something that solves a few problems well enough.

> generally consider tailwind to be a minimal local maximum

I feel like I've been nerd sniped trying to figure out what you mean by this.

It took a sec but I parsed "a minimal local maximum" as "from the set of all local maximums, this local maximum is near the bottom." Thus it is a local maximum, so any small change produces a worse result, but it is one of the worst local maximums you could have found.
> Thus it is a local maximum, so any small change produces a worse result

I have ML engineer brain, so to me any small change from a local maximum is an IMPROVEMENT (because we try to minimize loss functions).

Your reading makes more sense.

It's the lowest hill in the range of CSS frameworks, it's not reaching the heights of proper semantic CSS, but the valleys of haphazard, unsystematic style attributes are an even lower point.
What do you mean by "proper" and "semantic?" The CSS spec never took much of an opinion on how classes should be organized or mapped to elements in the DOM. It's a technical standard. The way people used to write high-abstraction CSS was completely a cultural convention that, in hindsight, overstayed its welcome.
Heh, fair, not a super clear phrase. What I’m trying to evoke is that tailwind has a few merits that do produce a local elevation on a few points of front end dev experience… but not much (and for some the tradeoffs cancel it out) and that more is possible.
The simple answer is that inline styles lack support for a bunch of basic stuff like pseudo-classes and media queries. I think the Tailwind team would tell you that they would love to have native web support for full-featured inline styles and that this would obviate many of the reasons to use Tailwind.
The real equivalent to that example would be something like:

  <RetroButton variant="primary" />
The style prop doesn't give you a design system, it's just raw values. Maybe you could use something like open props though.

https://open-props.style/

You can override classes, not so much inline css.
Is anyone ever going to override the "px-3" class?

You can also change styles through javascript if you really want to.

It just seems like we've lost the entire reason for css in the first place when every css attribute gets turned into it's own class.

Special cases in layout are a thing, happens a lot once you start nesting third-party widgets. So yes, there may be enough of a use-case for overriding px-3 that it wouldn't make sense to specifically omit it as a utility class -- to say nothing of it being useful as a utility class to begin with.

I only sprinkle in the very occasional tailwind class via UnoCSS. I don't have the fire in me to dig trenches for either side of this battle that some people want to make all this into.

> Is anyone ever going to override the "px-3" class?

No, but they will change things like their constants ("bg-neutral" or, in this case, the spacing constant).

This is some "put the comma in front of the column name in SQL" logic