A Supermarket Bag And a Truckload Of FOMO

The day was nearing to a close. The sun has already set, but that Friday evening in Amsterdam was still warm. Unusually warm, in fact, for those late days in March – as if spring decided to bless my piligrimage, for that piligrimage was not jovial.

I was sitting at a ramen joint, sipping on the broth. To my left, a blue, crinkled supermarket shopping bag was sitting solemnly, inconspicuously.

Inside that bag sat a slightly used Mac Studio, which I have just purchased to be able to edit CSS of my own application.

By the time that evening descended upon the south of Amsterdam, I have lost 3 days of my life trying to figure out why I was unable to edit CSS.

But let me rewind a bit.

Act 1: It starts with an app

I tend to listen to people. So when the time came to start a new app - which I haven’t done in quite some time - I knew that I would like it to be modern enough. Modern enough, even, for me to be able to delegate some work on it to others once the time comes.

So, while traveling, I started coding on that app - on the MacBook I carry with me normally. And one of the technologies I figured I should use - because all the cool kids do it, right? - was Tailwind. At work most of our setup is already on Tailwind, with some bits and bobs still not ported - but every time I needed to edit something in the specifically CSS-heavy parts of our UI, it was a bit of a struggle. I do understand the value proposition of the library, but I have it rough with learning modern CSS “two ways” - once for what Tailwind uses for dialect, and once for what it actually does.

To add insult to injury, one of my respected colleagues finally got enough of my lamentations (“why does it have to be so hard”) and – rightfully – told me, that if we are all working on a system which uses a certain piece of technology – I better get in line and learn it properly.

Since Tailwind 4 recently came out, and we would be upgrading our work app to that, I figured I could grab it to try and muster some layout in the new side project that was starting.

So far - so good. I found that there was a gem for Tailwind for Rails (perplexing, though, that a Rails app using Tailwind does not use that gem but instead uses https://github.com/rails/cssbundling-rails), so I went with that. And… it sorta worked. I liked that Tailwind 4 does not need all this baroque config setup, I liked not having to deal with Node, and I started slowly getting to grips with this RAF banter CSS:

Min-em-4! Wee-full! Fit-contain hover beegee-teal-100!

It was honestly OK (except that I had to keep about a dozen tabs of Tailwind docs open to do even a very basic layout). A couple of days were spent working, but I wanted to continue on my home setup - where I would have the luxury of a decent desk, 2 displays and some silence. The app needed to be ready in a few days for a customer tryout.

And that’s where things went south.

Act 2: SIGILL

Upon returning to Rotterdam, I checked out the code of the app onto my Trashcan Mac Pro - which is, by all accounts, one of the best Apple machines I have owned and used. After a GPU replacement it has been working super-reliably, day in, day out. It has 128GB of memory (how much is that in today’s M4 Ultra prices again?). It has more than enough cores to run things in parallel. It hosts Thunderbolt devices that I need and use without me having to replace them. It can also be disassembled without ripping adhesives, cleaned - and has helped me along through a multitude of projects and challenges.

The ease of doing SQLite3::Database.new(":memory:") would hardly be permissible without those 128 gigabytes, by the way.

And so I check out the app, and boot bin/dev. Upon which I see a curious bit of output in my terminal:

Illegal instruction: 4

Now, everything works fine on this app otherwise. convert works great. New ImageMagick did install (after recompliling half of Homebrew, but it did install fine). Rails boots. Node nodes. Databases database. Compilers compile. Why is it that Tailwind, which is a CSS preprocessor - like, for reals, this thing tokenizes text - would not run?

I start investigating, and before long I arrive at an interesting discovery. Tailwind did change majorly between 3 and 4, and not only in the way of syntax. See, it used to be a set of Node modules that you install - and a set of configs that you had to write. Tailwind 4, though, is no longer a Node module… or - rather - it still is a Node module, if you want it that way. But it is actually a binary! A 100MB binary (for parsing HTML and extracting class names from it).

And that binary has to come from some place. Upon some investigation - it turns out that the binary is, actually, a build of bun and an amalgamation of all the bits and bobs that comprise Tailwind. It also turns out that Bun is very…. modern. So modern, in fact, that it requires support for AVX2 instructions if you dare to run it on an Intel CPU. Which - of course - my MacPro does not support.

It also turned out that Linux users did get into exactly the same quagmire as I did. It turns out that nobody guarantees you that your build step will run on a box which has these instructions. And Docker or not, if your processor can’t do a %vpand - the thing will crash. The signal I saw - “illegal instruction”, or SIGILL - is a sign of this happening.

And - it turns ourt - Tailwind already uses bun-baseline on Linux because a lot of people complained that they are unable to run their builds on their CI systems, on their servers - but also: on some Linux machines people own. There is little reason a 10 year old CPU should not be capable of tokenizing HTML. As a matter of fact - it does so just fine, with pretty much anything libxml-based, as it does with Nokogiri in Rails - and a plethora of other applications.

I then assume that just creating an build with bun-baseline should fix it, and I open an issue. I also wanted to try building the binary myself, upon which I discovered that, to build a module that tokenizes CSS classes, I need:

…and upon installing all of the above - I found that the thing just would not run, because… it could not load one of the bun-related NPM modules.

I assumed that this issue was a skill issue on my part, and so I decided to wait for the maintainers to release a new version just putting the flag on. At this point, I already lost 1 day to debugging this.

Yes, exactly when I needed to do the actual app, I was already 1 day into the yak shave of figuring out why my CSS processing system crashes with a SIGILL. Mind you: not a plugin which does some fancy video decoding. Not a particle generator. A piece of code that chews text files and parses CSS classes used in those text files.

Act 3: CDN

To be able to proceed I had to divert for an interim solution. I was already committed to Tailwind on that app, and I had some styles defined. Now, even though I do have a bachelor’s in design, doing web layout for me is still hard. Very hard, in fact. The number of decisions you need to make, and the number of edge cases you must be considering - like, “design engineering” is a legitimate discipline.

So, I figured - Tailwind generates CSS classes. Yes, for doing its thing it scans your HTML and parses out the classes, and then copies them into the CSS output. But there surely, certainly, totally should be a static CSS file that I can damn link into my HTML to just have the same? During development I don’t really care it will be some MBs in size, as long as it has all the classes predefined. I don’t use @apply as I first wanted to test the waters just using Tailwind “as is”.

And it turns out Tailwind has such a file available - which is called PlayCDN. Supposedly, you link it into your HTML file and it “just works”.

Sort of. Because - as it turns out - Tailwind 4, while still being “zeroconfig” - which I wholly support (frontend tools have been absolutely egregious and inconsiderate with introducing configurations left, right and center) - is not so when you want to use an amalgamated version of it.

See, my layout has lost borders. All borders. All of them. No amount of twiddling could give them back short of forcing an inline style declaration - or setting my own CSS classes on the elements. As it turns out - there is such a thing as “preflight”, which is a sophisticated form of CSS reset. For some reason that I could not quite figure out, this “preflight” is permanently enabled in the CDN variant of Tailwind - and, moreover, it overrides the standard Tailwind classes.

Under normal use, Tailwind classes are supposed to override the preflight. That’s how CSS works. That’s how selector specificity works. Except here.

So - I stubbornly continued to fight the layouts, just keeping in the back of my head that “these borders will come back once this bun issue is sorted”

Act 4: All hope is lost.

A new release of Tailwind 4 comes out, which uses the bun-baseline build declaration. I immediately download it, set the path for the tailwindcss-rails gem, and prepare to revel in my reacquired ability of specifying CSS borders - which, for reasons of Amazing Design Technological Advancement, somehow had to be taken away by progress.

And what do I see in the terminal?

A SIGILL stubbornly stares at me. Using baseline bun did jack shit. Parsing my HTML still tries to use CPU instructions which I do not have available.

How could that be? Very simply, as it turns out. Bun just disabled compatibility with the last version of macOS that runs on my machine. Just like that. As this was done, the next logical consideration was as follows: since there are no machines without the AVX2 instruction set on the CPU that are supported by the next major macOS version, it is no use to have a build without those instructions!

But “to not break builds” the baseline declaration was kept in place. As an alias.

My computer with 128GB of RAM and 6 CPU cores was, as of summer 2024, officially not good enough to process CSS selectors.

At this stage I just wanted to proceed with the app, and I was sick to my stomach of having spent so much time on this issue. It did not bring me joy. It did not bring me convenience. And it did not advance my app one inch - all it gave me was a rabbit hole to dive into, head first, and a cup of frustration to drink to the very bottom.

Epilogue.

And this is what brings me to that faithful day. I was contemplating a new computer for a while, and was even eyeing an M3 Ultra MacStudio. Only it is so that the beast is on backorder here. On a 3 week backorder, in fact - as if you are buying an nVidia 5090 on its release date. So after a quick trip to Marktplaats I decided to settle this and found a barely-used M2 machine I could purchase. Today.

I brought it home, installed everything, figured out everything that was utterly borked on macOS Sequoia versus Monterey I used to run, and ran my app. The borders I have coded in Tailwind looked amazing. By this time, another day was spent on setting up this new machine. Another day, thus, which was not spent on moving the app forward.

The borders were amazing, but I did the only thing that - at this time - was sane. And which was, in fact, suggested by a couple of folks I deeply respect.

With some help of a friend I ripped Tailwind out of that app for good, and I promise - unless it flies high and warrants a team working on it, it is not going to be making a comeback.

What it is actually about

You may well think that I am “another grumpy Rails developer who hates modern frontend”. Or - that I am another “ageing Mac user who doesn’t want to move on with the times”. Or - “I know this guy, he never makes anything of value and just likes being angry and frustrated”. Or - he just hates JavaScript and frontend… Changing your mind is not my job You are very welcome, nobody is forcing you to read this article - and it was a pleasure to have you visit.

But there is a more important angle here that I want to share, as it matters. I don’t like swearing, but I will this time around - it fucking matters.

And it is this. We sometimes jump on using tech not because we need it. Not because we want to. But because we have FOMO. Either - we fear not being “current” and becoming “unhireable”, or - we fear not being “smart enough” in today’s market. One of the parts of being a good software maker is being “up on the latest” - you don’t have to use all of it, but - in the web world for sure - you need to have at least some basic understanding of how things work.

And sometimes, we even have colleagues who - for they don’t know better - start gently nudging us to explore if we don’t know. That nudging may, actually, turn into prodding - and then to mobbing - if you see that there is a technology choice that is not efficient, not great, and that is not a good fit - yet you have to use it anyway. Using and not loving is not considered enough.

Why techfluencing can be harmful

And then there are influencers. “If you are not using $tech 0 you are missing out.” “$tech is a game-changer - you can try to spend 10 years building half of it, badly, and won’t get nearly as good”.

I won’t be pointing fingers, but there is a cohort of “frontend-adjacent” influencers (at least 4) - who do this all the time.

And while they do get their exposure, their likes and subs and their premium followers - they either do not understand, how harmful their approach is - or they engage in it knowingly.

Because the biggest fear of may software folks is becoming irrelevant. And good software folks - ones on top of their game, whip-smart, kind, caring folks - do know that their own assessment of their relevance is likely not accurate. And they try to get it externally, “probe” the environment to know what’s about.

Who do they turn to? Their tech lead they no longer have? Their engineering manager who is waving JIRA tickets and asking questions about “5 years”? A community enthusiast who has just finished migrating his pet project from Elm to Rescript?

Likely not. But content from influencers is easily accessible and easily consumable. Moreover - it gets cooked in such a way as to be catchy, captivating and to sound authoritative. You don’t have to be an overt jerk to be dismissive and authoritative - the soft-spoken, passive-agressive tone, combined with a perfect haircut and great lighting, will also do the job. So will stealing other people’s ideas and capturing a community.

And if you consume it just enough you will start to second guess yourself. “Am I actually going in the right direction?” “Did I pick the right tool?” “Should I give this new fancy thing a whirl?”

And every junction - every decision - every infinitesimal choice you need to make when building your app - will be this second-guessing game.

And there is another reason for this second-guessing and for why those choices are difficult. If you have been at this game for long enough - you know that the hardest migrations and upgrades on your app are going to be the frontend upgrades. Node version changes, bundler version changes, syntax changes, changes of defaults. React router upgrades, create-react-app sunsetting, arrival-then-departure of SASS, then libsass, then Blueprint… A choice made badly is going to have consequences.

And the consequences can be quite sad, because these “fad of the day” frontend tools are great when you are making apps for clients - and then throw them over the fence and forget about. It is not going to be your problem when CSS suddenly stops working, or the React team deprecates an essential feature - it is going to be the problem of that poor sod who will be tasked with “figuring out how we fix this vulnerability”. By the time he gets started on it, you will be long gone - upgrading your current project from Elm to Rescript.

But if we are following the other direction that is, apparently, the right way to go (for many reasons) - and we build something for ourselves - we look into the future - and we see that there won’t be “that poor sod” dealing with this Byzantine pyramid of dependencies. It is going to be us. Pick wisely.

And the right way to pick, on your own project, is this - and I have learned it well, this time around:

Use what you already know know well. Think about your end goal. Is it to learn $tech? Or is it to build an app and get actual users? Do not blend the two. Use something that’s already melted into your fingers, unless you have space for going on adventures.

My biggest mistake was not “not learning Tailwind before”. It was not “not using Next.js”. Tailwind would have crashed just the same under turbopack as it did for me under Rails.

My biggest mistake was trying to combine “learning a new technology” with “delivering an app quickly”. Layout is hard already. Design is hard already. UX is hard already. Hard enough without all of these extra things on top.

But what about Tailwind, actually?

Tailwind is nice, and yet very painful at the same time. The things I really liked:

Things I found very painful:

Honestly, the two latter ones I could live with - and, after all, I could revert to @apply - which would drag me back to standard-CSS-but-having-to-express-it-in-Tailwind-for-some-reason. The first one - a dealbreaker.

Why? Well, because doing layout is hard already. I need to do a lot of energy to imagine how my app needs to look, how it should behave. That is already hard enough.

Doing the mental translation from that into CSS, and then another mental translation into Tailwind - is just too much, for little benefit.

What I did find from the Tailwind experience is that I absolutely do want 2 things:

For the former I will likely look for a good reset. For the latter - I already found a solution that works beautifully in Rails, does not require any preprocessors or Node modules whatsoever, and completely covers this use case (and more).

Thinking like a maintainer

And then there is one more thing. See, I know that shipping end-user software - like the Tailwind compiler - if that software is written in an interpreted language - is fucking hard. I know that getting binaries built for older versions of macOS is an absolute abomination. - seriously, just read that article if you don’t believe me. And I absolutely admire Brad, for example, who stubbornly went and built the Terminalwire binaries using tebako.

When I worked on Tracksperanto, one of the most popular questions I got from studios was how to install it without having to install Ruby and Rubygems. Like.. shipping scripted products is hard!

But here is the rub: if you play that game, you have to play it well. It is not for nothing the TypeScript compiler is getting moved to Go. It is not for nothing that esbuild, which I consider to be the only web bundler product that was done right, from the start - is written in Go. You can dislike Rob Pike’s attitudes all you want - and I do - but one thing is undeniable: Go is absolutely stellar at the monobinary-for-every-imaginable-platform.

And if you play that game, having both instruction set compatibility and ABI compatibility with the target platforms does, sorta-kinda, become your problem. So, exactly because I do know what I am talking about, if I were in this situation:

And yes, I know it is incredibly tempting to just keep developing your software in “that language you know and everybody else in my ecosystem knows”. It can also become a bad bet exactly when you see that this approach has downsides. And yes, it is now your problem too - to be aware of those downsides, and pick the users’ convenience over your comfort as developer.

And it’s not like there never was a precedent before, in an completely adjacent field too: remember libsass? While it did not pursue a monobinary approach - but needed to speed up builds with A Gawddam’ Shitton Of SCSS - and it was C++.

So: yes, I do not find the Tailwind team made the right tradeoffs in picking a platform for Tailwind 4. The desire was noble, but the end result is not worth it. And yes - Tailwind is free, and bashing people on the web is free too - and as per DHH I am not owed anything. But I am entitled to my opinions, and I am well within my right to call out things I consider mistakes. So there.

My personal opinion is that - at least in this case - the adoption of carrier runtime and tooling was not done responsibly.

What about the box then?

It’s a mixed bag. USB audio drops out on reboot. System Settings is a shitshow. Wacom tablet support is a drama - and I have to use an older driver version for my Intuos, where the Wacom Center just crashes on startup. The famous product manager mano-a-mano of Reactions…

I can get by. It really is not what I have been dreaming of. And I resent the fact that Tailwind 4 was the thing that made me buy it. It also had to be an older model, because the new ones were on a 4 week backorder in any decent configuration.

Having a CSS pre-processor was not worth that money, and not worth having that resentment.

A more important lesson

…for me - is to mute the influencers. There is a list of a handful of folks - they are good folks, but what they are doing is absolutely toxic for what I need to do - namely, build stuff. So I am not only going to go all @levelsio on it and ignore the “best practice” advice – I will also mute all of them. I don’t want to second-guess myself at every bundle add and every brew install. I don’t want to go to sleep with an anxiety that every new tech is going to make me unhireable. And I don’t want to end every day with a feeling that I am a bad developer just because I don’t use nuxt-tailwind-immer-zod-whatever.

Because I know that I can do just fine. If I am allowed space to build.

I know they are not doing this out of malice, but I have to protect my creative spirits at least to some extent.