Why am I only now learning about Milligram? It is included by default! Why the Tailwind hype?

Did you just look at the example? Because right after explaining the example it goes on to say

Now I know what you’re thinking, “this is an atrocity, what a horrible mess!” and you’re right, it’s kind of ugly. In fact it’s just about impossible to think this is a good idea the first time you see it — you have to actually try it .

I’ve never been great at css in any form, but learning tailwind I’ve definitely come to appreciate it. You definitely need to combine tailwind with some sort of templating system to give you components, but Phoenix has this built in with the new heex stuff.

After working with it, I genuinely believe in all of the benefits that they list:

  • You aren’t wasting energy inventing class names. No more adding silly class names like sidebar-inner-wrapper just to be able to style something, and no more agonizing over the perfect abstract name for something that’s really just a flex container.
  • Your CSS stops growing. Using a traditional approach, your CSS files get bigger every time you add a new feature. With utilities, everything is reusable so you rarely need to write new CSS.
  • Making changes feels safer. CSS is global and you never know what you’re breaking when you make a change. Classes in your HTML are local, so you can change them without worrying about something else breaking.

The issue many people have with that is that it’s pure strawman. Nothing stops you from using utility classes: most design system have them. Many Tailwind also acknowledge that it can quickly becomes a mess and that’s why @apply is great but at that point you’re back at square one and it’s just another sass-like utility library.

CSS is not more hard than Elixir unless you refuse to learn the tool you’re using. Browser inconsistencies, on the other hand, are hell.


I’m not saying tailwind is the only library that gives you utility based classes, but I’ve never seen another framework that’s so based around them. I tried writing my own utility classes before using tailwind but you end up making so many little decisions that using your own quickly becomes overwhelming. Using a library and now you have to compare, and from what I’ve seen tailwind has the best docs, the best examples, and a large community.

I know @apply exists but I don’t really see a reason to use it if you have a templating system that can wrap up your HTML and your classes into one. That’s why I said it’s basically mandatory to use tailwind with a templating system, fortunately we have that in Phoenix by default.

I don’t think css is harder to learn, I think css is harder to maintain in a larger software project. I think that tailwind helps with this by largely eliminating the need to write custom css. If you disagree, then that’s fine, I merely wanted to point out that just looking at the example and going “ugh that’s gross” without actually trying it out for yourself is something that the docs explicitly call out.


To be fair, looking at examples and reading blog posts about Tailwind is what got me excited about it. Actually trying it out for myself is what turned me off.


That’s exactly the point of TailwindCSS for me. Think of it not as a CSS package, but as a fancy macro language to write your own CSS in a concise way. I don’t use any of the TailwindCSS’s utility classes in my html; My classes are all semantic and that are mostly composed by @apply directives.

1 Like

Don’t get me wrong, Tailwind is excellent at what it does, it has a clear objective and it does it well.
I just don’t like how they bash the already existing tools based on pure strawman. People have different needs and different tools fulfill them. There’s no need from any side to bash each other so hard.

I don’t have much to say about the tech itself, people have been doing the very same as Tailwind for ages, either as small sass utility frameworks or as ad-hoc utilities for particular design systems(see GitHub primer).

Then what is the difference between:

 .btn {
   @apply py-2 px-4 font-semibold rounded-lg shadow-md;


.btn {
  padding: 1rem .25rem;
  font-weight: 600;
  border-radius: .5rem;
  --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  box-shadow: 0 0 #0000, 0 0 #0000, var(--shadow);

With exception that I do not need to learn yet another language to understand what px-4 actually mean. We do not use single-letter variable names for a reason.


Less LOC.

I am of the opinion that you should write every line in your styles. I tried to do that with plain CSS and then I gave up because it was so damn hard. Then I used several CSS packages, milligram being one of them.

Tailwind brought back the level of control I need, without writing thousands of lines code in a language I barely know and certainly don’t like. Tailwind is far from perfect: It is dog slow to build and had crazy breakage between major revisions. But it is the best I found so far.


Yes. The advantages of TailwindCSS are obvious to me. Some context: I’m a backend dev, who “learned” HTML and CSS a long time ago. I don’t have a big incentive to stay up to date with latest and greatest advancements in CSS and their browser compatibility.

Say I want a nice shadow on a <div>. So my options are:

  • With Tailwind:
  • With CSS:
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
box-shadow: 0 0 #0000, 0 0 #0000, var(--shadow);

Even if I learned what that piece of CSS does, it’s impossible for me to write it correctly from the top of my head the next time. Not to mention parsing what it does - I can’t see forest for the trees.

So I chose to learn Tailwind instead of CSS. I takes a day to remember the most common classes and the IDE will give me the right suggestion most of the time (shadow, border, truncate, etc.). It’s not to say that Tailwind replaces CSS - you still need to grok things like the flex box. But the speed improvement is huge.


px-4 is not a variable, it does not change its meaning.

Padding is one of the most common attributes, so it makes sense to have a short syntax for it (p-2 instead of padding-2). Same goes for margin, width and height.

I get it, Tailwind is not your thing.


Less LOC is huge. For my entire site I have about 30 lines of custom CSS. The rest is just Tailwind with some custom brand related colors loaded into the config and named.

Previous CSS file was 2,000 lines and kept growing because it was easier to just add something new than make sense if exactly what was in there and where it was used. Dramatically simplified maintenance.

Additionally, there are hundreds of CSS frameworks out there. Everybody has a preference for which one is best too. It results in analysis paralysis trying to figure out which one you should invest the time to learn and apply to your project, when you should be thinking only about what you need to do.

My expectation is for Tailwind to be the last CSS framework I’ll ever learn. I don’t get into Tailwind UI because the components are plenty easy to build on their own thanks to Tailwind.

The design ebook that those guys wrote does a great job of explaining the reasons you’d build the way that Tailwind is built. I read the entire thing and couldn’t argue with any of it. That’s rare for me.


The same for me. That is exactly why I like new CSS features in combination with PostCSS - it makes writing everything much easier and I do not need to care much about compatibility, as tooling takes care about it for me. All of that while I still write “good-old-plain” CSS.

You can do it like:

:root {
  --shadow-md: 0 0 #0000, 0 0 #0000, 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);

.btn {
  box-shadow: --shadow-md;

Still IMHO much cleaner than just @apply shadow-md as it doesn’t tell me what kind of shadow it is, and unless I will know other TailwindCSS module names I have no way to know just by looking at the code what kind of shadow it will be (whether text or box shadow). And CSS variables are supported in all browsers nowadays, so that mean, that I can have some “semantic” classes and then in subcomponents control everything just via them. IMHO super useful while keeping classes in HTML semantically meaningful and in CSS keeping the information what value changes which property.


The difference there is that tailwind gives you a standard naming scheme for size and shade variations. Just like with a Rails-like framework benefits a team by adhering to conventions, so does Tailwind.

Aside from that, if differences in browsers appear, Tailwind can normalize the underlying CSS without any need for changes to your code.

Can you build all of the classes yourself to use a naming scheme that you like better? Sure.

Should you…I suppose that’s up to you.


I personally don’t care at all about “semantic” CSS. When I’m writing vanilla CSS I do, but I’m certainly glad to have the relief from it that Tailwind brings. I want to make it clear that I’ve been writing CSS since it came out and while I would not call myself a “ninja” at it, I know how to use it—and I love Tailwind!

For me, “don’t use a style attribute” is old dogma. I still don’t think you should use a style attribute but not because it’s “mixing concerns” but because it’s not capable of unleashing the full power of CSS (hovers, etc).

As with many “rules” in computer programming, their meanings can vary. For me, the “separation of concerns” I care about is the separation of business concerns, not the separation of languages/tech types. With the popularization of component-based design, this becomes easy—we can have everything (design, template, data, logic) for a small business concern all in one place[0]. React (ugh) gave us JSX (yay!) and now utility frameworks like Tailwind (or other types of like React’s styled components) let us have our CSS in our components too.

The other thing I love about Tailwind comes down to Sandi Metz’s good old “duplication is better than the wrong abstraction” quote. With Tailwind, it’s really easy to get going very quickly and build up a consistent design without putting a whole bunch of upfront thought into it. Once patterns start to emerge, you can extract them to @apply as necessary (you’ll probably want to do this for colours and sizes and whatnot) though often you’ll probably get away with leaving everything as it and extracting it to a component.

In any event, I hear the reasons why people who don’t like Tailwind and I get it. I certainly don’t use it on small projects that aren’t going to have a lot of CSS, but its design philosophy is sound and it’s certainly not just for people who don’t know how to write CSS.

[0] This may sound a bit like OO to you but that is another discussion.


In many places Semantic UI is anything but semantic. Most glaring example is lists. This is their idea of a list by default:

<div class="ui list">
  <div class="item">Apples</div>
  <div class="item">Pears</div>
  <div class="item">Oranges</div>


As for Tailwind, Dan Abramov of React has a nice summary: https://twitter.com/dan_abramov/status/1452324924421550080


Currently I’m using a combination of Tailwind and standard CSS. Tailwind is well documented and has a lot of utility classes that I’d end up writing myself anyway. I use it whenever I need something simple like margins, padding, setting display to flex and so on. As soon as a combination of those utility classes actually has a specific meaning (or I end up with too many on a single element) I create my own class and just write regular CSS. Most of the Tailwind classes are easy to remember, although there are some that I always forget or trip me up.

I don’t like using @apply or other Tailwind specific syntax. I try to stay as close to the standards as possible unless the benefits are really worth it. With custom properties you can avoid duplication and make your own CSS and Tailwind play nice together.

You could do something like the following:

:root {
  --spacing-1: 0.2em;
  --spacing-2: calc(var(--spacing-1) * 2);

You can actually use those custom properties in your Tailwind configuration. I do this for spacing, colors and so on. So if I have something that needs padding that’s exactly the same as p-2 I could just do:

.card {
  padding: var(--spacing-2);
  /* more styling here */

Now I don’t have to worry about keeping values in sync between my own CSS and Tailwind.

Tailwind mainly showed me the value that utility classes can have. As with everything it has benefits and drawbacks. By sticking to standard syntax as much as possible it saves me from having to think about utility classes right now but it still allows me to pretty easily take it out in the future if that would be necessary.

1 Like

Regarding the “Why am I only now learning about Milligram?” question, specifically, I was surprised to learn that Milligram is mentioned nowhere in the guides, so I submitted a quick PR to address this:

Hopefully this helps things a bit in the future :slight_smile:

** Replied to the wrong person by mistake and I thought I could quickly delete and re-submit but that appears to not be possible. Adding this disclaimer so I can get around the “Your body is too similar to what you already posted” message when I try to re-submit :sweat_smile:


I like your approach. I think the strength of tailwind is about providing a well thought out subset of CSS that is practical to use, but repeating oneself all the time in order to be able to copy paste html with their styles still feel horrible to me. If I need the same html and style I would rather create a component for that anyway…

1 Like

the core library does not have those commercial licenses

try daisyui as a starting point

1 Like

Well, I’m back to using Tailwind now. It turns out that I love it. My previous comments were due to not having gotten the hang of it. It is like using inline styles, except in a more controlled and succinct way – and as soon as my HTML gets too hairy I just pull the styles out into my own tailwind components file that I’m building as I go along. It feels to me that this is “real CSS” – i.e., what CSS should have been all along.