To have 'mix phx.new --no-tailwind' to just do not use Tailwind, but still create basic CSS

Greetings Everyone!!!

A little bit of my background so it could be easier to understand where my comments are coming from, and to take them with a grain of salt…or lots of.

Background

I started coding back in 1989. A few years later I was writing code with VIM in Unix: FoxBase+ and C. So, I am very used to code on a simple and fun programming language, and in a not so fun but powerful language, hence, I am used to allocate memory when needed and freeing it when no longer needed, and yes, I still use Short integers when available: The very first computer I used had 128Kb of RAM, the Tandy Color Computer 3. Some habits are hard to break, and I really want my programs to be thin and light. I am barely starting with Elixir, Phoenix, LiveView, HTML and CSS, thus… I really see no point on using Tailwind. I started using Linux in the mid '90s because I love the freedom it provides, so I totally agree that everybody is free to choose if they want to use certain tool, or not. Everybody is free to chose their poison. No questions asked (my VERY PERSONAL point of view).

Suggestion

Could it be possible to have mix to create code like this:

# === core_components.ex ===
  def modal(assigns) do
    ~H"""
    <div
    [...]

      # WITH Tailwind (Hard-coded styles, I guess)
      class="relative z-50 hidden"

      # WITHOUT Tailwind
      class="sample_modal"
# ===  priv/static/assets/app.css ===
/* Created on the fly WITH Tailwind */
.relative{
  position: relative;
}
.z-50{
  z-index: 50;
}
.hidden{
  display: none;
}

/* Hard-coded WITHOUT Tailwind */
.sample_modal {
  position: relative;
  z-index: 50;
  display: none;
}

Explanation

As of now, it seems like I am being punished for not using Tailwind.

I have a few plain text lines above my form when the put_flash is invoked (:info, or :error) because there are no styles at all, and I have no clue about what styles to use to have those lines to show up as Green/Red pop-ups with CSS.

Either, 1) I will have to study what all those Tailwind classes to know what CSS they are using in order to replicate the basic behavior, or 2) I will have to create another project using Tailwind to get all those classes and then gather the CSS needed to achieve the basic behavior.

Both cases, as I mentioned before, seem like I am being punished for not using Tailwind. As far as I understand the parameter is --no-tailwind, not --no-css.

Best regards,

Greg

1 Like

For the record I agree with you, and I think defaulting to Tailwind was a mistake which will have to be rectified some day.

But with that said, the default components are provided mainly as an example to get started. A lot of people are using Tailwind, so they wrote the “example” components in Tailwind.

You can simply remove the tailwind classes and style the components yourself, or write your own using them as an example. Personally, the first thing I do in a new Phoenix project is nuke core_components.ex and import my own :slight_smile:

Nearly all of Tailwind’s classes have very obvious 1:1 mappings with CSS, down to the name - this probably won’t be as difficult as you think. Their docs are also very clear.

1 Like

Well, as I mentioned, I am barely starting with Web Development (HTML, CSS, etc). Last week it took me a while to center my login form. I had a very hard time trying to center a div or a form. Even, when watching tutorials to do that specifically.

So, at the end I think it was more the surprise of me asking not to use a framework, and finding out no CSS code was generated at all.

I think Tailwind is a very reasonable choice since as mentioned, components are meant purely for getting started/prototyping. Even if you stick with Tailwind and stick with CoreComponents, you will likely still end up modifying them pretty heavily. Providing a style sheet would add a non-trivial maintenance burden on the Phoenix team and general, if you know CSS, you will have very little trouble converting (I actually just went through this process at work).

I sounds like you are more interested in learning CSS. That’s more of a tough one as I think it’s safe to say that is not a goal of core components. If Phoenix were way more popular maybe it could be?

2 Likes

Clearly I scanned too fast - I figured you knew CSS but not Tailwind.

I think your criticism is reasonable.

You and everyone else here - this is the “how do I exit vim” of webdev :slight_smile:

If you want to center horizontally, you put display: flex; justify-content: center; on a parent div. Vertically, use align-content: center;.

You have to be careful with tutorials because there are, like, four generations of solutions to this problem. You can also use grid but it can be more complicated.

Grid is actually one less line! display: grid; place-items: center; (for both vertical and horizontal)

Ya, it’s really quite funny how long it took CSS to get a way to properly centre things. It’s why I kept using tables for long after it’s cool: it could actually do it from the beginning! So it was hard to buy the idea that maybe it was hard to implement, but of course I don’t actually know and maybe it was.

2 Likes

There have been excellent answers already. What I wanted to mention is that you can also provide your own templates to be used with mix phx.gen.

They shall go under priv/ and have the same path as they have in the Phoenix Git repository (look for the tag matching the version you’re using).

More details:


If you go this way, the burden of maintaining the templates in sync with what the generators expect (which can and sometimes changes between versions) is on you and not the Phoenix team/contributors.

Happy coding!

(PS: yeah, aligning divs might the million dollar question in web development!)

2 Likes

Lol I did not even know about place-*, will definitely be switching to that. To match my example you would want place-content: center, though. Depends on what you’re centering of course.

Grid definitely goes off the rails quite quickly for a newbie, though. It also technically requires more passes than flexbox IIRC, so it’s a bit slower (not that anyone cares).

Tables have become the absolute worst for actual tabular data because they play so poorly with the rest of modern CSS. Honestly they probably need to bite the bullet and create a new “flextable” or something so we can just move on.

Of course you can make fake tables out of flexboxes and grids but you lose the semantics, which sucks. Actually, maybe allowing display: grid; on a table element is the way out…

3 Likes

You can think of it as a perk for using Tailwind. For me, Tailwind is like CSS in training wheel. I used it, I learned CSS through it, then I got rid of it.

1 Like

Allow me to disagree with you, and please, remember that I am barely starting with HTML&CSS, and I have a very different view of the Web Development world, and … most likely … I am wrong.

You mentioned…

I failed miserably to understand why the non-trivial maintenance burden: I see quite a lot empty Tailwind classes mentioned in the core_components.ex file, most likely they are all hard-coded. It is not actually a Style Sheet, but kind of. It’s already there, almost. Somebody put them there. They already have the group of classes for certain component. For me that sounds a lot like a Style Sheet.

Please, don’t read me wrong. I understand is an extra task to hard-code an actual app.css file, but, as long as you don’t change any style in the core_components.ex file, the app.css file will be the same.
For example, let’s say we have this code:

# === core_components.ex ===
   class="relative z-50 hidden"

Using Tailwind that’s all the core developer will write, the CSS code will be written by Tailwind auto-magically. It will write those three classes with one CSS property each.

# ===  priv/static/assets/app.css ===
.relative{
  position: relative;
}
.z-50{
  z-index: 50;
}
.hidden{
  display: none;
}

When not using Tailwind, that class could be named something like:

# === core_components.ex ===
   class="sample_modal"

and those three CSS properties should be gathered into one class:

.sample_modal {
  position: relative;
  z-index: 50;
  display: none;
}

I understand that, as of now. somebody will have to do that, to gather all the Tailwind classes into a CSS class for each component. But, just once.

After that if the core developers update a component, i.e., they add/remove a Tailwind class of a component, they should have to add/remove that property in the hard-coded app.css file. Is linear, not exponential. If somebody changes a classes group line:

# === core_components.ex ===
   class="relative z-50 hidden p-5"    # <--- "p-5" has been added.

Well, go and add that property to the hard-coded class for this component in case --no-tailwind is used:

.sample_modal {
  position: relative;
  z-index: 50;
  display: none;
  padding: 5px;   /* <----- New Line of code */
}

Again, I am very used to allocate memory on top of my functions, and right after that go to the end of the function and free that memory space when no longer needed. So, I don’t see the non-trivial maintenance burden.

But don’t put too much attention to my comments. I am just a old - grumpy - non-web-developer having a hard time while starting in Web Development, but still happy Elixir, Phoenix and LiveView exist.

Best regards,

Greg

Can you give some examples? I do agree that doing fairly essential UX things like fixing headers can be a pain—you can’t just fix thead you have to know about stuff like border collapse which is annoying. The biggest thing I’ve had a problem with keeping columns fixed.

With the even changing landscape of the web, some things I say here may be a little outdated but here it is:

Tailwind isn’t just utility classes, it takes care of a whole host of problems presented by vanilla CSS which renders very differently between browsers. Phoenix maintaining a vanilla CSS file means not only thinking about cross-browser, but dealing with all the github issues that are bound to arise. These complaints won’t just be that something isn’t working in some obscure browser but also people taking issue with how the CSS itself is written. People have already taken issue with how the Tailwind classes are used and there is a pretty ironic thing about that which touches on your “Somebody put them there” comment: The Tailwind team itself wrote the Tailwind for Phoenix!

Now, there are solutions for dealing all the hellish browser discrepancies that CSS brings on us, but these mostly involve some form of JS build tool and NPM. Phoenix used to ship with webpack and would configure it and all that and grab stuff from NPM but a couple of years ago they decided to simplify everything by dropping all that. This was smart as it’s pretty much impossible to up the ever changing JS landscape. Instead, it now ships with the base esbuild (no plugins) and vendors topbar.js. Around this same time, the Tailwind team had their back again by releasing the tailwind CLI that didn’t depend on NPM, so that Phoenix users (as well as Laravel and some other frameworks that ship with Tailwind) could get it without having to rely on NPM.

Whether you like it or not, Tailwind is the most popular CSS framework among Phoenix users so it makes absolute sense for it to be the defacto solution out of the box. Unfortunately, Phoenix isn’t big enough to tailor its generators to learning CSS (though that would indeed be nice) and, as mentioned before, if you know CSS it’s trivial to shift it over. And of course the whole thing that many people who start serious projects end up deleting or heavily modifying Core Components.

As for comments on Tailwind itself, that is a discussion I am looooong over having :sweat_smile: It sounds like I’m not quite as old as you but possible that I’m more grumpy :smiley: Unlike you I really started programming seriously with web dev in 1998, so I’ve been using CSS from the beginning. I always liked it just fine, warts and all, but the first time I saw Tailwind I liked it immediately. I also still use vanilla CSS for projects, though, and enjoy that too (it’s getting better and better!)

1 Like

There is only one problem with CSS really, which is that any non-trivial CSS file is not maintainable. Many smart people knew this and have tried to fix it, but I consider every attempts, including Tailwind, still failures. Let me make an analogy for us old-timers: CSS is like Makefile in this regard. Do you want to maintain a 1000 line Makefile? Considering In software, 1000 line is next to nothing.

Now the Phoenix team admit that making s CSS solution is not among their core competencies, and picked one of the lesser evils. And they leave the option to rip everything off wide open. I cannot think of a better approach myself.

2 Likes

One that always bugs me is managing how free space is allocated. This is super easy with FlexBox and Grid but very annoying with tables.

There are many more annoyances with padding and borders and so on. I don’t remember them all off the top of my head, but every time I’m fighting with a table my first thought is always “if only this was a Grid”…

I was trying to exercise some restraint in not getting into this but since it seems the discussion has drifted in this direction anyway, I’d like to argue in defense of OP and against Tailwind as a default.

I think Tailwind was a mistake - both for the “industry” and for Phoenix. Like many I actually quite liked using Tailwind in the past, but it is fundamentally a degenerate solution. Throwing away standards in favor of a bespoke class generator tool solves some of the problems with CSS, but at the cost of, well, throwing away standards. If you use Tailwind you are always one step behind real CSS - one abstraction layer deep for no real reason. It creates real confusion, like you see in this thread. Also, the classes-in-HTML are a mess - a tired criticism, but it’s still, like, true.

There are two problems Tailwind solves: one is maintainability, which it solves by co-locating CSS styles with HTML. Inline styles almost solve this problem, but they don’t. Tailwind does.

The second problem Tailwind solves is generating a good design system (spacing, colors, font scales) out of the box. Tailwind does this really well, well enough that I think the colors more than anything were the main driver of adoption. The maintainability is a bonus.

The problem, again, is that Tailwind is a degenerate, nonstandard solution. The correct solution to maintainability is scoped styles. Not the scoped styles in the CSS spec, which unfortunately are kind-of useless, but component-scoped styles. In Phoenix, this means scoping styles to heex templates (e.g. function components, views, liveviews, etc).

To my knowledge, the first attempt to implement this with Phoenix was the Surface project. Unfortunately, that project contains a lot of other functionality, much of which was then ported to Phoenix (heex), and I don’t think a lot of people even know about it.

Again to my knowledge, the second attempt was my own. It is actually possible to hack scoped styles on top of Phoenix components by hijacking the heex macros and extracting a <style> tag from the content. I have been using this library to build my own apps for a while now and I can confirm that the approach scales. Note that this library is not production-ready for anyone except me - in particular, the CSS parser was a proof of concept and does not emit useful error messages (and there are some other annoyances). I intend to clean it up for others to use at some point.

Importantly, corp_style (my library) also implements design systems as a set of conveniences for generating CSS variables, with inheritance. This works really well, and combined with component-scoped styles it is a worthy Tailwind replacement.

With this approach the CSS you write is real CSS, with real CSS variables. It’s easy to learn and does not pollute your source HTML (in devtools it just looks like normal CSS). I really feel this is the approach Phoenix should take down the road. It is the framework’s responsibility to implement component functionality, not delegate to Tailwind.

5 Likes

Do you implement scoped styles using randomly generated class name prefix like what Surface and Svelte was doing, or do you use a shadow DOM for each scope?

1 Like

I generate a random (hash of the function name) attribute for each component, and inject it into each HTML element of the component (including slots) at compile time, using a special macro (sigil ~SH, for styled heex). The macro then returns AST from sigil_H with the updated content. It’s really quite clever, I’m rather proud of the simplicity of it.

The resulting HTML would look like <div sh-XXXXXX class="foo" /> and the CSS is scoped using an attribute selector, e.g. .foo[sh-XXXXXX] {}.

This is also how Surface and Vue implemented scoped styles, at least when I last checked. I don’t know about Svelte.

What was really important to me was not the technical implementation but whether the approach actually “scales”, and the UX works. I had many questions, like whether it was a good idea to scope the content of slots, and how often I would need to use escape hatches (global styles).

Many thousands of lines of code later I can confirm that I pretty much nailed it first try. Scoping slot styles to the component passing in the slot turns out to be very useful, and I have used the :global escape hatch only a handful of times.

I guess it’s finally time to dust the library off and rewrite the parser :slight_smile: Soon!

3 Likes

You are obviously correct; I only have vague recollection of seeing some random looking strings. The downside of doing this is that it only prevent the scoped CSS polluting the outside, but the outside rules can still affect the inner scope, often in unexpected ways.

I prefer to use a shadow DOM (full isolation, yay!), However, it is a heavier touch and may not be suitable in many scenarios.

I’m not gonna bite on debating Tailwind :grin: I’d be fine with scoped styles—though I would hesitate to call them the “correct” solution—and your solution looks really nice and I would be proud of it too :slight_smile: But as far as what is shipped with Phoenix, it still doesn’t solve the problem that it would need a heavier setup as opposed to just vanilla esbuild + tailwind CLI in order to get a sane vanilla CSS file working in a reliable way.

And just to comment on this, it’s moot point as core components are not meant for production, so there is no responsibility of the framework. They’re just there for people to play around with Phoenix or prototype with. Tailwind is the lowest barrier for that so I still firmly believe it is the (current) right solution for phx_new, especially with niche adoption that Phoenix has. Perhaps if a dedicated CSS fanatic joined the core team things could be different.

100%. Would love to see Phoenix adopt scoped styles and drop tailwind as the default. It should be opt in.