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
Most Liked
Eiji
I don’t like it for a few reasons:
-
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. -
.cssfiles 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 usestyleattribute instead ofclassforTailwind. TheTailwindis overusingclassattribute making it harder to read especially for people that does not know all of it’s shorter naming. -
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-clickwhich sounds more like anti-pattern than a standard. -
Tailwinddefines 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 advancedCSSandSCSSrules 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. -
I remember that
José Valimmentioned that the environment could be simplified (by getting rid of anodestaff and so on) and it really happened and then they have added their own dependencies (Tailwind,DaisyUI). I would prefer to seePhoenixtemplates usingSCSSfeatures and variables that we can simply reuse in our code rather than what we have right now. TheDaisyUIcould be replaced with variables and a simpleColocated Hook, that saves theme toLocalStorage. Not only it would be a very good for learning purposes by using existingLiveViewpatterns, but also it would not be atheme frameworkthat works over other framework. -
Tailwindmay have compatibility bugs. It happened at least once and it was mentioned some time ago on forum for oldmacOSversion. NeitherCSSnorSCSScauses such a problems. Such problems are unacceptable for me. I don’t want that aJSfile 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?
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.)
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-4immediately 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:
paddingwill 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.
Popular in Polls
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









