I've been playing around with Tailwind CSS a lot since it's release, and so far it's been really impressive to see its growth over the last few years, along with some of the neat things people are making with it.
Overall I've been pretty happy with it, I even purchased a licence for Tailwind UI to help me develop with it more quickly. However, sometimes I find myself a little frustrated at it, so I decided it would be fun to rebuild the frontend on my Ruby Calendar project to see if I can pinpoint my annoyances.
What I love about Tailwind CSS
- The documentation! Seriously Adam Wathan done a fantastic job with it. Often when I just want to check how to do something in plain CSS, I use their documentation as a reference point.
- Responsive utility variants are very cool. Being able to apply a CSS class to just big screens by using
lg:
before my class names is super lovely! - Using
@apply
within my regular CSS. It feels like such a concise way to write CSS, I'm a big fan of it. - Less context switching between CSS & HTML files. I didn't realise how much cognitive load I had from jumping between different file types all day. Being able to just look at a single file for everything I was working on helped me get into that awesome "hyper focus" zone a lot more easily.
- The examples I can copy & paste (which look the same in my project), is a massive time saver. It feels like I can very quickly cobble together a frontend MVP for a project without too much effort.
What I don't love
- Preflight. I always find it's super aggressive, this is probably because I come from a background where I've used normalize.css a lot. Having to setup base styling for semantic HTML feels tedious.
- JavaScript Dependencies. I had a project on Tailwind V1 which used React, which I upgraded to V2. Unfortunately I had an out of date library which made upgrading harder then I expected. In the end, I was deep in JavaScript code trying to figure out why my CSS wasn't working. It made me feel super unproductive.
- Other developers use it inconsistently. I've picked up a few projects using Tailwind, and it takes a lot time to feel "at home" in the codebase. In one project, I felt like the other developer just used every class everywhere, which made it very hard to achieve that "Happy Developer" moment.
- CSS Purging. I've been caught out in the past by setting up the purging, then moving a few files around to only discover I quietly broken a few pages in production. I think better CI tooling could solve this, but I'm also feeling I'd rather avoid the risk to start with.
- Staying visually consistent. I'm the worst for using all the sizing & colour variants available to me, I need a limitations to avoid making an inconsistent monster.
Replacing it with vanilla-er CSS
My plan was to remove Tailwind over a weekend, using a mix of normalize.css
, CSS Variables & mixins, all combined into a single CSS file using PostCSS.
I had already started converting my CSS to follow the BEM approach using @apply
, so I was able to take my purged CSS & break it into smaller files. I then went through and moved all the things like spacing, fonts & colours I was using into CSS Variables.
Why vanilla-er CSS?
I wanted to be as close to simple vanilla CSS as possible, the reason is I worked on a project which hadn't had the styling touched in about 5 years, and then when I edited the styles.css
file...it just changed what I expected it to. It was a really interesting "Oh? Would you look at that!" type experience.
Obviously, I do like a few low touch tools to help reduce duplication. But I do want to aim for a codebase where in 5 years time, it'll be easy to pickup. I think the best way to do that is by keeping things as simple as possible.
Naming CSS Variables
Naming was pretty hard! For my fonts & spacing, I ended up copying the approach of using numbered a scale which is often used in Tailwind.
/* variables/fonts.css */
:root {
--fonts-serif: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-size-1: 0.75rem;
--font-size-2: 0.875rem;
--font-size-3: 1rem;
--font-size-4: 1.125rem;
--font-size-5: 1.25rem;
--font-size-6: 1.5rem;
--font-size-7: 1.875rem;
}
/* variables/spacing.css */
:root {
--spacing-1: 0.25rem;
--spacing-2: 0.5rem;
--spacing-3: 0.75rem;
--spacing-4: 1rem;
--spacing-5: 1.25rem;
--spacing-6: 1.5rem;
}
Naming Colours
Naming the colours was a tad harder. I've never been a fan of calling a variable "blue", then making it the colour blue. The reason is often in the future that "blue" may end up not being blue, which makes things messy.
Instead I copied the approach of Bootstrap of having "Primary", "Secondary" & "Tertiary" colours, however I explicitly named the variables to hint that the colour is intended to be used as a background or text.
/* variables/colours.css */
:root {
--background-primary: #3c2aaa;
--background-secondary: #1d2938;
--background-secondary-lightest: #3f4a5b;
--background-secondary-light: #232c39;
--background-secondary-dark: #0f192c;
--background-tertiary: #d0d5dc;
--background-tertiary-light: #ffffff;
--text-primary: #f9fafb;
--text-primary-darker: #a1a1ab;
--link-primary: #c5d1ff;
--border-primary: #273241;
}
I did also add *-light
& *-dark
variations of these colours (For use within hovers & whatnot), though I do want to come back and improve the suffixes I chose.
Ideally I want to achieve variable names which make other developers say out loud "This is so obvious, I know exactly what this is for". If anyone has any ideas please let me know :)
Media Queries
I wanted a way to pre-define the common screen sizes I'd use when building out my responsive designs.
To solve this I used postcss-preset-env, which allowed me to define a "custom-media" with the name --viewport-lg
and the value (min-width: 1024px)
. As postcss-preset-env
also supported nested CSS this allowed for some pretty readable CSS.
/* components/footer.css */
.footer {
padding: var(--spacing-4);
padding-bottom: var(--spacing-6);
text-align: center;
@media (--viewport-lg) {
text-align: left;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
Mixins
Pretty soon I did feel like I was duplicating CSS & mixing utility classes with my semantically named classes within my HTML.
However, I found the solution was to use mixins via postcss-mixins.
/* mixins/list-inline.css */
@define-mixin list-inline {
list-style: none;
margin-left: calc(var(--spacing-2) * -1);
margin-right: calc(var(--spacing-2) * -1);
padding: 0;
> li {
display: inline-block;
padding: 0 var(--spacing-2);
}
}
/* components/navbar.css */
/**
* @markup
* <ul class="navbar__links">
* <li><a href="/groups">All Groups</a></li>
* <li><a href="/add-event">Add Event</a></li>
* </ul>
*/
.navbar__links {
@mixin list-inline;
margin-top: 0;
margin-bottom: 0;
display: none;
@media (--viewport-md) {
display: block;
margin-left: auto;
}
}
This allowed me to have semantic looking class names which included common CSS snippets, while being able to override things as required. It also gave me the potential to programmatically generate a styleguide from the comments within my CSS file, which I'm wildly excited about.
Easier Importing
As my project grew, I found I could glob import files via postcss-import-ext-glob, which made my index.css
file much more maintainable:
/* index.css */
@import-glob "variables/*.css";
/* External Libraries imported from node_modules */
@import-glob 'normalize.css';
@import-glob "base/*.css";
@import-glob "components/*.css";
@import-glob "utilities/*.css";
Final Thoughts
Overall, I'm very happy with this CSS & HTML approach. I can look at a snippet of HTML, see the CSS classes being used & know exactly which files I need to edit to change them. I feel very in control of the CSS I'm writing as a result of that.
While reviewing my new HTML & CSS, I really like that I'm not staring at a wall of CSS class names any more. Plus if I wanted to make any changes to the colours, spacing or fonts, I feel confident that I won't need to change lots of files to see the desired visual change. Instead I can open the variables/
folder, then the appropriately named file & edit just a few lines of CSS.
I also found the final size of the generated CSS was about the same as before, so I'm very happy with that.
I did come to appreciate how much time Tailwind had saved me while I was prototyping my design (and how it made me think more in a component mindset), but I think it's also a very sharp tool which requires a lot of discipline to use effectively on projects.
Finally, I think I'd still be happy to work on a Tailwind based project. However, I totally feel a bit more confident in saying "We could just use CSS Variables & Mixins instead if we wanted".
Top comments (23)
This is absolutely the most jarring part of Tailwind, and unfortunate that it's inherently the first thing you experience when starting with Tailwind.
I'm honestly surprised that there isn't a collection of standard styles (at least I haven't heard of any yet) that has a few baseline starters to "fix" preflight and offer a quick starting point to get some standard styling back.
So much yes @terabytetiger !
When I first installed Bootstrap the first experience was "Everything looks pretty decent", but Tailwind often feels like a I'm starting from a blank slate. I think I'm in the same boat as you where I kind of expect semantic HTML to render nicely.
Tailwind isn't designed to give you a "generic" base style - it wants you to create your own visuals exactly as you need them - For us this is perfect because every design we produce is unique, if we had to remove all this extra fluff added for us it would be even more of a pain.
If you need a base style - then you create your own that you can pull into future projects, or find a UI Library that does it - there's plenty that people have already created :)
In researching something else, I have now stumbled upon tailwindcss-typography.netlify.app/ which (kind of) resolves most of my complaints here.
I wish the
.prose
didn't have to be added everywhere to apply it, but I guess it's better than nothing 🤷♂️I agree. Utilities are great, and Tailwind took it to the next level. However, something doesn't let me go all in for that. I still prefer Bootstrap. They have introduced utility classes way back, but maybe because of a lack of proper documentation and example, people didn't know exactly what to do with them.
Because of this, I've created a project in which I included a bunch of utilities using the Bootstrap 5 API. Now you have the best from the two worlds: basic components and utility classes for faster customization. Here's a link to the docs: webpixels.io/docs/css/1.0/installa...
Tailwind, like Bootstrap and all the rest, is for developers who can't be arsed to learn proper CSS. It's a crutch. And when you can't walk, a crutch is great. But eventually you begin to see the downsides of walking with a crutch. You want more.
In a long career as a developer, I have used all kinds of frameworks and libraries. I've mostly given up on frameworks. They make it easy to get started, but eventually they get in your way and make things much harder.
Libraries are a bit better, but depending on other devs to maintain their code, keep it secure and tested, keep it up to date, etc. is a fool's errand. So I have concluded that the fewer dependencies, the better. Add one only when you need most or all of its functionality and writing it from scratch would be onerous.
There is nothing that any of these UI libraries gives me that I can't add myself quickly and easily -- and I've used them all going back to the first versions of scriptaculous. With CSS variables and CSS modules, there's really no reason to use anything else -- unless you simply can't be bothered to learn CSS.
As for the cognitive load of context switching between JS/TS, HTML, and CSS... please. Writing HTML as JSX or CSS as CSS-in-JS is the same cognitive load as putting it in a separate file. Using separate files keeps each clean and focused. It's not about separation of concerns but about separation of duties.
Svelte does a nice job of separating the duties, but then forces them into a single file. That's too bad.
When I code, I keep my component in one pane and my CSS (module) in the pane next to it. If I've drawn my boundaries correctly, all the code in each pane fits on one screen (admittedly, I have big screens). It's all right in front of me. And if I keep to the single responsibility principle, those screens stay short and focused.
That is how you reduce cognitive load -- not by memorizing yet another DSL so you can add ten classes to a component. And Tailwind promotes breakpoints (sm, lg, etc.), which is clearly not the right way to do things. Interfaces should adapt smoothly to changes in the size and aspect ratio of the portal and the zoom.
I know Tailwind is the new hotness right now. I've been forced to use it more than once already. But in a couple of years it will seem obsolete and foolish. Why wait?
Bookmarking this for viewing later. If I ever get this advanced at writing css this seems like a great guide. Excellent post!
Thank you @kirkcodes !
It looks like we have a shift towards using more "vanilla" code. Both in CSS, JS and HTML in general. I really like that trend. Vanilla is a loaded word though. I think it has become a hallmark of something "not great". But I think many of us has experienced the actual "not great" feeling of going in to maintain a webapp written perhaps 2 years ago by using all the libraries&frameworks that were cool back then: If you chose to upgrade the libbraries&frameworks, you are in for one helluva ride. On the other hand, if those webapps were written using mostly vanilla tech, you may end up not having to do a single upgrade. Go Vanilla!
I seriously hope we are on a trend towards more "vanilla" code with CSS, JS, and HTML. I'm appalled at the current state of front end over-engineering.
Every few month I look into Tailwind but after reading and trying to style my first components, I always cannot believe how long and ugly this classes look and feel. Utilities are okay for some things. I have my own grid system so I can already put anything into the right columns as I wrote HTML but after that I switch to CSS and write the styles of the component - without almost ever having to change the HTML again, with auto reloading. This is no way worse than Tailwind. I just don't get the advantage. But of course I know - it is not your fault TW, It is mine 🙃
Do you think you'd be up for revising the "Preflight" and "Javascript Dependencies" points to something potentially more (excuse the term) "accurate" ?
Preflight - this is completely optional, just don't include the directive for it - in fact you can add normalize if you like? Maybe just a revision to say you can disable or replace it if you want.
Javascript dependencies - If you want to avoid adding TW to your build chain, simply use the official CLI tool which lets you build on the fly without touching the rest of your code base. From memory it's a simple as "npx @tailwindcss/cli build" (plus some extra args)
I appreciate these are things that have caught you out, but they're not faults of Tailwind, they're caused by how it's been used. If you drive a car into a river and complain it sank - it's not the cars fault :P
While this is true, I don't think it's particularly relevant to the author's point - which is that when you follow the Installation page's instructions, you end up with fully reset styles and it can be jarring to new users.
The first line of the installation page which discusses this option is:
and under the "Using Tailwind via CDN" section there's this statement:
Everything the author has run into that you're calling out is from following the recommended installation guide in the Tailwind docs and will align with the experience of many new users.
Not really interested in any excuses tbf - The fact of the matter is sharing false information.
If people with less experience read that - they will repeat and take it for truth.
If a balanced argument is provided, they will be educated with both sides.
The only thing I like about Tailwind is that you don’t have to switch between files while building a component for instance...
This is what I love in Marko, but also Svelte or Vue.
Svelte is awesome
@zzoukk That is a really nice feature (React has the same appeal to me)! I have tried using a split tab in my code editor, but it never quite feels the same :/
Great article @mikerogers0 !!
This was a game changer for me honestly. Not writing a wall of messy CSS it does bring me so much pleasure and makes writing CSS so much cooler.
Thank you @bogdaaamn !
I can't live without glob import now, it makes everything better!
great article, Mike. I've learned a lot!
Thank you @clandau 🎉🙏
I made the following library just before someone referred me to this helpful post :)
github.com/zvizvi/freewindcss