Remix.run Logo
9dev 3 hours ago

This is such a bikeshedding debate. While you don't recommend it, projects with Tailwind work. Over years. You can onboard new developers to it, able to contribute productively immediately. Likewise, you can pick up work after months or years and don't have to remember or rediscover how your styling layer works.

The conventions and class names come really naturally fast, and you can always look it up. It's just not as a big of a problem people make it.

But the most ridiculous part of the article I found the cascade complaint:

  <p class="text-red-500 text-green-500">I am some text</p>
Yes, this does not work. Why should it?! There is not a single use case where this is a good idea! In classic CSS, you might want to override something based on modifier classes, but that is just not a thing with Tailwind! If you end up programmatically layering class names, you're looking at a code smell. Instead, you want to use attribute or state modifiers, like `aria-hidden:opacity-0`.
bko 19 minutes ago | parent | next [-]

> If you end up programmatically layering class names, you're looking at a code smell.

Why wouldn't you? Suppose you have Button class with `btn btn-primary`. The Button accepts className override so you can do <Button className='btn-secondary'>. In this case you use tailwind-merge or clsx. Your Button implementation would be clsx('btn btn-primary', className), in which case right most class name prevails.

Other than that, I agree tailwind is great. It lets you keep things all in one place. Names aren't particularly difficult to memorize and reason about and autocomplete does a great job. Build utility classes or proper components to encapsulate the styling and you're fine. What benefit do I have going to a css file to review my button styling over the actual Button component?

zelphirkalt 2 hours ago | parent | prev | next [-]

I wouldn't even write such a class name. It smells like bad styling and layouting already. A CSS class should convey some semantic meaning. I would name it after the thing that should be red or green, not "red"/"green". That doesn't tell me anything. Maybe its name could be something like "danger" or "active" or something. Also the 500 looks very sus. Responsive design is best when it avoids such hardcoded numbers and depends on its content and dynamic viewport width calculations, and perhaps a few minimum sizes, if necessary to decide when things float, wrap, shrink, grow, etc.

It's a red flag (ha).

francislavoie an hour ago | parent [-]

Absolutely not. Semantic classes are the wrong way to go. Your components (and their props API) are what encode the semantics. With Tailwind you never need to try to come up with a name for anything relating to style, you just use utility classes. It reduces cognitive load significantly.

SebastianKra 28 minutes ago | parent | prev | next [-]

I agree that all of this is bikeshedding, but the cascade complaint is actually real.

It's so common, that there is an expensive runtime package, `tailwind-merge`, that is used by default in the most popular component library.

marcus_cemes 2 hours ago | parent | prev | next [-]

I can see this argument both ways. On the one hand, logically you'd expect the last assignment to take precedence, like the style attribute. On the other, conflicting styles don't make sense.

The random outcome, entirely dependent on the Tailwind generation internals, is the worst of all worlds though, it's just an unfortunate side-effect of relying on cascading sheets to drive atomic styles.

yurishimo 2 hours ago | parent [-]

It’s random sure, but it’s also reliably random. This is one of the few footguns that exist in tailwind and luckily it’s super easy to catch. The IDE plugins will give you a warning if you apply multiple classes that manipulate the same attribute and there is no reason you also can’t catch it in CI.

Is it ideal? I guess not, but there are a lot of weird gotchas and footguns in CSS too. Just because they’re in the language doesn’t make them magically more or less of a problem. For a similar weird mental shift, consider adding @starting-style and the native popover open attribute. All the good tutorials online have a warning about cascade order because as a human reading it, it can seem wrong or backwards.

Kevin Powell’s most recent video about animating display: none covers it if you want a concrete example.

boxed an hour ago | parent | prev | next [-]

Projects with a single gigantic CSS file also work and you can onboard people etc. That's not an argument.

9dev an hour ago | parent [-]

they do work… for any value of work. I have been in the industry for a while, and I've never seen a project which doesn't slowly devolve into fights with the cascade over time - arbitrary abstraction layers, ad-hoc organization patterns, developer-specific conventions, inconsistent naming, and so on. All of this usually creeps in the more a project grows. At some point, someone adds postcss or scss or another preprocessor and starts splitting files, and then a whole new set of new, made-as-you-go conventions comes in: Files per component or per view? Or rather per architecture concern? Usually a mix of them. As files are split, the class naming gets even more inconsistent.

And at that point, onboarding a new developer always means wasted time on understanding this whole organically grown set of bespoke conventions, class names, patterns, hierarchies, and so on.

Tailwind does not really have this pattern of deterioration growing along both time and complexity, it stays consistent on both axes. So it's definitely an argument IMHO.

guessmyname 2 hours ago | parent | prev [-]

The fact that “text-red-500 text-green-500” resolves by stylesheet source order rather than anything visible in the markup means the “locality of behavior” promise quietly breaks exactly when you compose components dynamically, which is why tailwind-merge exists at all, and needing a runtime dependency to answer “which of my two classes wins?” is a real design smell, not a bikeshed.

9dev an hour ago | parent | next [-]

That is kind of my point, though—you don't need tailwind-merge, really. There are two cases it solves:

One, adding classes from the outside to an encapsulated component with its own, internal classes, to make sure the outside-applied classes take priority. Either use the !important modifier on them (`ms-auto!`), put the components into a container div with the classes for layout concerns, or even better: Figure out why you need to make styling changes to a component that cannot be expressed via props. I would generally recommend components to not have outside-element styling like margins in them anyway, which most often fights with positioning later.

Two, merging prop-derived styling with base styles - for example for a `size: 'sm'|'lg'` prop. It's tempting to just use tailwind-merge here:

  const classList = tailwindMerge(
    'p-4',
    size == 'sm' && 'p-2',
    size == 'lg' && 'p-6',
  );
But that isn't necessary at all: The better alternative would be to use data attributes for visual concerns and built-in or aria attributes for interaction states. There are almost always element attributes that can represent what you want to have correctly, and data- where there are not. Then, you can just add a class accordingly:

  <span 
    class="p-4 data-[size=sm]:p-2 data-[size=lg]:p-6 data-active:font-bold"
    data-size={size}
    data-active={active}
  />
And you'll end up with easier to maintain components that derive their styles from the CSS cascade alone.
francislavoie an hour ago | parent [-]

I would reach for class-variance-authority (aka cva) instead for stuff like a size prop, it declaratively solves that.

9dev an hour ago | parent [-]

Interesting, I didn't know that project yet! I'll give it a thorough read.

gf000 2 hours ago | parent | prev [-]

It's not true - agents will visibly struggle more with worse code bases. If anything, certain kind of maintainability matters more in this era, than before, because it is so much cheaper to just generate a huge amount of low quality code.

2 hours ago | parent [-]
[deleted]