arcanemachine

arcanemachine

Do you prefer to use Tailwind in your Phoenix projects? (Poll)

This was the first question on my mind when I saw this other thread by @AstonJ.

I’m curious how many Phoenix users actually like using Tailwind.

Do you prefer to use Tailwind in your Phoenix projects?

  • Yes
  • No
0 voters

Most Liked

Eiji

Eiji

I don’t like it for a few reasons:

  1. It’s forced by default in a way it’s not easy to remove. Standalone it would be not a big deal if we talk only about a single generator, but if we have to rewrite each generated template then we simply waste time and it’s against Elixir’s 10x rule where we focus on writing the app’s logic.

  2. .css files is not some kind of an old standard that’s currently useless - it exist for a reason. Styles are supposed to be separated from templates and not be part of them. If that would be the case then we would use style attribute instead of class for Tailwind. The Tailwind is overusing class attribute making it harder to read especially for people that does not know all of it’s shorter naming.

  3. I’m definitely ok with an idea to keep template code and it’s styles in the same place as I said in another post, but same place (with extra macro above template function or so) does not mean part of template. This makes templates code longer and therefore harder to read. Think if someone would not format templates and would put all element attributes in same line with class as a first attribute. This way we would have to scroll the code just to find things like phx-click which sounds more like anti-pattern than a standard.

  4. Tailwind defines a shortcuts for styles. It does not introduce anything besides it. It would be nice if above template function there is a single line macro, but in a more complex case it’s just a long string of abbrevations our brain has to parse over and over again. It’s not using advanced CSS and SCSS rules like nesting, variables, counters, scopes and many more. It’s kind of pre-generated CSS abbrevations you have to configure and a framework that forces you a way of writing your code. It’s more like a framework that forces you to do things in specific way rather than a library that gives you useful tools.

  5. I remember that José Valim mentioned that the environment could be simplified (by getting rid of a node staff and so on) and it really happened and then they have added their own dependencies (Tailwind, DaisyUI). I would prefer to see Phoenix templates using SCSS features and variables that we can simply reuse in our code rather than what we have right now. The DaisyUI could be replaced with variables and a simple Colocated Hook, that saves theme to LocalStorage. Not only it would be a very good for learning purposes by using existing LiveView patterns, but also it would not be a theme framework that works over other framework.

  6. Tailwind may have compatibility bugs. It happened at least once and it was mentioned some time ago on forum for old macOS version. Neither CSS nor SCSS causes such a problems. Such problems are unacceptable for me. I don’t want that a JS file would not work, because I have too old or too new hardware..

I just wanted to explain that Tailwind and DaisyUI projects solves some problems in a way that does not fits Elixir/Phoenix/LiveView well. SCSS mixins and other functions are more than enough to make styles much shorter. esbuild for JavaScript and dart_sass for SCSS seams like more than enough dependencies we really need. The rest are some extra macros to make templates next to styles and a set of Phoenix variables, SCSS mixins and so on …

Please pay attention how SCSS extends CSS and how Tailwind limits CSS. There are many things that Tailwind needs a workaround to make some things working which just means more short naming than ever needed. If you ever wrote a CSS do you really feel a need to make @media query any shorter? What about a new CSS features that would be not supported until Tailwind would introduce another workaround for them? What’s the reason of using a terrible long class attribute if at the end in complex styles you still write your own CSS?

Tailwind was never an option for me. It’s not about how good or bad it really is. It’s about the way you want to write a code. It’s not like that everyday you fill a need to write some OOP code in Elixir, right?

12
Post #3
garrison

garrison

Separating the styles from the markup was always a terrible idea. There was a notion that you could mark up your documents “semantically” with elements and then re-use styles across documents and documents across styles. This has systematically failed. Nobody does this.

So instead you’re just left cross-referencing a giant CSS file with a giant set of HTML templates. Total mess. Zero benefit. Bad idea.

In fact, just using the style attribute would be a wonderful solution, except for the fact that it sucks. It’s ancient and it wasn’t designed for this.

The correct way to do composability and re-use is not to apply the same styles to different elements but to group elements and their styles into components and compose there. This is what everyone does now. We do it with Phoenix too.

Tailwind, as a library, solves three problems:

  • Styles should be co-located with components
  • Styles should be composable (no name conflicts)
  • Design systems should work out of the box

Unfortunately, the utility class approach is fundamentally degenerate: it can never fully express CSS, and CSS is the thing which browser vendors actually support. So you are always playing catch-up.

The series of increasingly ridiculous hacks they have come up with to fit CSS into a class-shaped hole is something to behold. First they needed a compiler to strip the unused classes. Then they started dynamically generating styles at runtime with a made-up class syntax. So then they had to start shipping binaries just to compile CSS, and now they’ve even had to introduce a new Rust codebase to keep things performant.

There is a much simpler solution: web frameworks are already responsible for providing a component framework (we have HEEx). They should also be responsible for providing a scoped CSS implementation to go with it, and an extensible design system out of the box.

(See my comment in the other thread (linked in the OP) for an example of how this can be done.)

10
Post #5
hauleth

hauleth

I do not see the value of Tailwind at all. It still requires me to learn CSS, but on top of that I also need to learn their own custom nomenclature full of abbreviations and custom units. If I need to do that, then why not just simply learn CSS and skip all the Tailwind magic?

When I do p-7 what is that 7 mean (or maybe that is -7? Who knows)? How does it relate to something like size-48? If I want to have some colour to stuff I need to use grey-700? How does that relate to grey-300 or orange-300? If I do grey-700 on purple-300 will it have enough contrast or what?

The numbers, Mason. What do they mean?

Modern CSS also adds a lot of useful stuff that provides a simple way to avoid most of issues that people had previously.

There are even more upcoming things like typed attr() CSS function, which will allow me to use just HTML attributes for customisation if needed.

In addition to above - with semantic CSS I can style using ARIA attributes, so it will “force” me to implement proper a11y features from day one, and when I come back to the code, I can read what does the values mean instead of deciphering arcane shortcuts.

<form>
  <input type="text">
  <button type="submit">Action</button>
</form>

Vs

<div class="container display-flex my-md mx-sm">
  <form class="form shadow-md my-md mx-sm align-center">
    <div class="input-wrapper border-radius-sm">
      <input type="text" class="input text-color-gray placeholder-color-light-gray focus-outline-blue">
    </div>
    <div class="button-wrapper border-radius-sm">
      <button type="submit" class="button bg-color-blue text-color-white focus-light-blue hover-light-blue">
        Action
      </button>
    </div>
  </form>
</div>

Honestly - TailwindCSS, with all tooling that is created around it, seems like designed for AI slop and to move complexity around (and IMHO in the wrong direction).

And now, whenever I start new Phoenix project, it is yet another thing that I need to go through codebase and remove. That is becoming so painful, that I probably will need to sit and just write my own generator, because “default one” became so full of pointless cruft.

garrison

garrison

This is exactly the same attitude I was referring to above. I do not understand this insistence on refusing to study the good parts of a popular solution. You are essentially just telling people “I don’t care about the problems you have” and then are surprised when they ignore you and use the thing that solves the problems they have!

Here are the problems, again:

  1. Styles should be co-located with components
  2. Styles should be composable (no name conflicts)
  3. There should be a configurable design system that works out of the box

Let’s now review how modern CSS solves zero of these problems:

  1. There is no way to co-locate styles with components (the style attribute is bad)
  2. Nesting does not solve the naming problem and also still does not compose properly (think about what would happen if you used HEEX slots)
  3. Lol, lmao even. The only genuine improvement here is oklch, and that’s a stretch.

It’s literally just a scale. It’s configurable! What does rem mean? How many physical pixels are in a logical pixel?

They are just color scales, chosen by hand to look decent as it turns out that used to be really hard. Maybe we can finally move on from this nightmare with oklch, though. But this has nothing to do with Tailwind, many do the same thing with variables (I do).

This is nonsense, Tailwind became mega-popular well before the LLM apocalypse.

Agreed. But does --no-tailwind not work anymore? I haven’t generated a Phoenix project recently.

frankdugan3

frankdugan3

First, I feel like a few things need to be clarified about Tailwind:

  • Tailwind prefixes however you like: @import "tailwindcss" prefix(tw); <div class="tw:flex">
  • As of v3, it no longer uses tree-shaking. Utilities are only added when detected being used
  • As of v4, it uses native CSS layers, so it’s very amenable to using alongside semantic CSS as a utility class library
  • As of v4, all of the utilities are based on native CSS variables. You can add to these or selectively include what you like. This makes it very easy to leverage in semantic CSS.
  • When searching the documentation, p-4 immediately refers to the padding section as the first result. Using a full class will be much more successful in terms of results.
  • Reverse-searching is also pretty reliable: padding will bring you directly to the padding section as the first result.

The v4 release of Tailwind undid a lot of the weird Tailwind magic, and now it’s a small handful of directives and the rest is normal CSS. As long as you don’t use @apply, it’s actually very easy to migrate a project away from Tailwind simply by copying in the variables and utilities from your last build.

I find Tailwind convenient, if used in a more CSS-centric way than the Tailwind authors recommend. I feel the following value is provided:

  • A standard suite of color, spacing and size variables
  • A standard suite of utility classes
  • A fast, standalone preprocessor
  • Easy customization of built-in themes, utilities, etc.

The value of the above is magnified by the fact the majority of developers are already familiar with these standards.

I definitely agree with the distaste for piling all the utilities into the class attributes. Although that’s what’s recommended, with v4 there’s no real reason to do that just to use Tailwind. Here’s a simplified version of how I like to use Tailwind.

def simple_form(assigns) do
  ~H"""
  <form class={["core-simple-form", @class]}>
    <!-- ... -->
  </form>
  """
end

:root {
  // Synchronize CSS with Tailwind's dark mode -- works with any dark mode strategy
  color-scheme: light;
  @variant dark {
    color-scheme: dark;
  }
}

// Using this layer preserves full functionality of utility class overrides
@layer components {
  .core-simple-form {
    display: grid;
    padding: calc(var(--spacing) * 4);
    background: light-dark(var(--color-zinc-50), var(--color-zinc-950));
  }
}

As mentioned earlier, native CSS @scope or simply very specific selectors can easily be used to handle scoping component selectors from leaking into slots.

Now, I can use the generic form component in a particular place, and have the value of one-off utilities. In particular, breakpoints:

<.simple_form class="lg:grid-cols-2 2xl:grid-cols-3">
  <fieldset class="col-span-full" field={@form[:wide]}>

I have found this to be a very productive way to do things. The reusable components have clean CSS, and I can keep the one-off styling neatly in the component with a handfull of classes.

Regarding co-locating the CSS, the hooks/JS implementation uses a generic MacroComponent. It’s currently undocumented because I think the API and some details are still under revision. Once it’s ready for prime-time, we will be able to easily implement CSS colocation via a MacroComponent of our choosing. Personally, I don’t mind writing my classes for components in a CSS file all that much. I also don’t find much difficulty with naming component classes. Some variant of module + component name is pretty effective for namespacing in any app I’ve worked on (ERP, E-commerce, etc.) That said, a macro component can solve both of those problems in any way the developer sees fit.

I’m not really interested in convincing anyone whether to love/hate use/don’t use Tailwind. It’s a tool, I can work with and without it. Just sharing what I’ve found convenient and useful.

TLDR: We can have both semantic CSS and Tailwind, and it’s actually pretty ergonomic IMO.

Where Next?

Popular in Polls Top

josefrichter
There are two polls and a third question… 1) Displays poll 2) Orientation poll 3) Configuration Please let us know how you are using you...
New
thojanssens1
In an attempt to figure out what are the preferred GraphQL clients for Elixir developers, I made this poll below. If you’re using GraphQL...
New
arcanemachine
Now that the results are in from the Tailwind-only poll, what do you think of the combination of Tailwind and daisyUI? poll
New
New
AstonJ
poll asdf Extendable version manager with support for Ruby, Node.js, Elixir, Erlang &amp; more Manage multiple runtime versions with a ...
New
AstonJ
We last posted a poll like this in 2017 - here’s an updated version for 2022 - this one is slightly different.. You can select up to 3 -...
New
JEG2
Which topics would you most like to see covered in downloadable Livebook guides (like the How to Train Your Scrappy Programmer series I r...
New
ryanswapp
I think it would be interesting to see how most people are using Phoenix in production. Do you use it as an API backend for a SPA? Or do ...
New
hst337
Do you use hot-reload/recompilation in production? Please note that this poll is only about BEAM’s internal features of runtime upgrades ...
New
AstonJ
After seeing some of the responses in this thread, I’m curious how much RAM you’re opting for and whether there is any specific reason fo...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New

We're in Beta

About us Mission Statement