1. Locality of behavior - the classes are the description of the style once you know tailwind, and they live where the thing they're styling is. I can look at a template and know pretty well how it is going to look without having to go lookup a sites personal bespoke CSS and match what's written there to the markup.
2. It's the same classes as you move from project to project. I have tons of small one-off apps I maintain, all using tailwind and I don't have to figure out the custom CSS classes and their meanings. It is a knowledge set that reduces friction between projects and I really need things that reduce cognitive load like that.
3. No dead CSS. There are tools to help with this in regular CSS but I just don't have to think about it with tailwind and that's nice. I work on my templates and the CSS is updated and minifed on the fly without extra steps (or unnecessary CSS).
4. Easy to build a personal component library that doesn't also require relevant CSS to be bundles with it. For me this one is pretty great. Thanks to template engines (for me it's Django's, but Jinja or others are grand too) I have a simple place to pull components (really just template partials) and widgets I've made and they just work because the underlying classes are the same. There are some exceptions around color schemes and the like but now all my little components take a colour scheme arg and voila, no proballo.
I think the key here is that a description of the visual is embedded in the markup. That lets the actual mark-up delivered to the front-end describe pretty much the whole kit. With server-side rendering ( I use HTMX ) I can even write unittests that responses contain expected markup, including CSS classes (I built a few helper assertions that all of my test suites use) which gives me some interesting checks.
I really ultimately don't think there is one correct way to do things. The things I value may be utterly irrelevant to another developer.
I see the point, but really this is solved better by having a solid foundation of UI components (might be an existing UI-kit or own thing, a piece of CSS anyway), and then you can use the same-ish classes over many sites.
What I find with any site is that without a good foundation you're bound to repeat same styles over and over, and with one you don't get any point with Tailwind.
Or when I'm working two contract gigs. I can spec things out for one and turn it loose and trust it. Then work more closely with deepseek on the other project.
This was my earliest use-case for LLMs and it remains to this day as the most compelling value proposition of all the fancy new LLMs.
I have always tried to abide by DRY in my programming career with the huge exception of writing unit tests. I made the mistake, early in my career when Test-Driven Development was all the rage, of making unit tests reflect the inheritance structure of the actual code. It just made sense. Needless to say, it quickly descended into the most bizarre manifestation of inheritance hell as tests randomly failed with no correlation to the changes done in the core code.
Hence, I resolved to make unit tests the huge exception to DRY. The more straightforward your tests are, the better. Endeavor that each test method up to a test class should read understandably on its own.
This, of course, made tests quite a mechanical chore to write. Which makes it the perfect use case for these large, verbose, and humorless daemons. Bonus that they are also very good at vibing out the set-up needed for a test so I can focus on specifying the test cases I want rather than setting up mock after stub after fake.
The output is also very easy to review and verify. I see no moral quandary in this kind of usage.
I've been doing full-stack Dev (mostly Django though I foolishly had a brief moment where I kinda thought Flask was okay then fell for the same dumb thing briefly with FastAPI) for 20+ years and don't feel marketable.
I went from jQuery to a brief dalliance with Angular to HTMX+_HyperScript. Everyone wants full stack Devs to use react and struggle eternally with insane dependency trees and challenging client side state management.
I like to build things that can be maintained in perpetuity by small teams.
I'm full stack for 19 years and I love React, and I've been laid off for 1.5 years now. This is a terrible job market right now.
That doesn't mean you don't have good skills, it just means that too many people have them. It happens from time to time in every industrial, for all skills.
Obviously, I don't have any good advice about how to deal with it.
I've been an AppSec engineer for about 12 years, but it wasn't until about 5 years ago I started working somewhere that actually paid a market rate. I wasn't living paycheck to paycheck for the first 7 years, but certainly wasn't putting much away.
Now, I've got nearly a full year of after-tax paychecks in savings. I could easily go ~18 months without pay without a change in lifestyle. I could stretch it out to 3 years with some belt tightening.
In about 6 more years, the house will be paid off and any savings I have could last even longer.
That's not rare for a SWE in NA. Difficult is another matter. Plenty of SWE's in NA are now staring down the barrel of not being able to afford their own home for much longer (or to afford their first home).
If you've been working for three years as a junior and just put down a big down payment on a house you can barely afford, yes, you're going to have problems.
If in 19 years as a SWE, you haven't saved up a lot of money, you are one or more of:
1. Incredibly unfortunate in the jobs you've been taking.
2. Have made some incredibly bad investments.
3. Are spending like a sailor, burning every dollar as it comes in.
4. Have gone through one or more absolute life catastrophes.
... Then yes, you are also likely to have financial problems if you're out of work for two years.
I don't mean to say that these are incredibly uncommon. They aren't, especially in people chasing the startup carrot and ending up with nothing but the brown, sticky bit.
But I think it's fair to say that a large number of people in the profession should have managed to avoid all four. (With #3 being the main 'avoidable' culprit, and with #4 being largely unavoidable.)
Consider that somehow, people making less than a quarter of our prevailing wages manage to live... Fairly comfortably.
The parent comment mentioned North America. This is huge. Tech salaries in Europe are half what they are in NA. In India, they're like 1/4 to 1/3.
Saving is absolutely important, especially in such a layoff-ridden industry. You should really strive to get at least 6 months of living expenses into savings.
My company pays 10k a year for an Indian contractor, full time. I don't know what their agency pays them, but it can't be even 1/4 of a typical NA SWE salary. More like 1/12th.
Most of Europe has social security and reasonable health care. Cities even have working transit systems. Living costs are massively different. I expect the costs in India will also be.
Not that it means you'll be raking in a lot of surplus money, but that's also not directly tied to the size of your salary.
I don't know; I am not sure I'm marketable in THIS particular market.
As for FastAPI bring a mistake - that's an overgeneralisation to be sure. It has it's uses.
My first issue is that it falls into the same kind of small footprint as Flask. Every Flask project I work on slowly reinvents Django via a combination of plugins of varied quality and custom code/plugins. Get some Auth plugins, build step for manifest static files, add in Jinja2, grab an ORM like SQLAlchemy (or hopefully PeeWee), a migration system, test runners with fixtures/dB integration and rollback and on and on and on.
FastAPI is operating more at that level but also adds the often unnecessary complexity of async. In Python this is a cooperative setup meaning you have to yield to the event loop yourself (otherwise despite being "async" it blocks). Plus with a webapp all async often does is let you hammer your services (i.e. dispatch more queries to your poor DB) harder. The actual performance improvements don't manifest so much at scale as people often think. Plus you end up with a whole second set of ways to call functions and... makes me pine for gevent.
There are absolutely cases for this kind of async, even in webapps, but it's often not actually that helpful in places that it's used (and doesn't actually need to be everywhere). Good development imo means picking the right tool for the job rather than jumping on hype trains.
Thank you for this answer, it saved me hours of experimenting.
And bonus points for bashing MongoDB, of course. Every single project that I worked on where MongoDB was used, also had MongoDB as the single largest constraint and operations time sink.
Nothing I have ever used has a comparable dependency tree nightmare.
reply