Tachyons to Tailwind

I migrated my personal website (this one) from Tachyons to Tailwind.

These are some quick unedited notes about the experience.

I think migrating from an inline CSS framework (like Tachyons) is actually harder than migrating an old-school site with a big hunk of CSS styles. Because the CSS is everywhere. I had to migrate over 100 files. This was good to do, but also kind of sucked. It makes me really hope Tailwind sticks around forever because I never want to migrate CSS across a million files like this.

Note that even finding the CSS isn’t trivial, because I have JavaScript and Nunjucks templates which build the CSS with functions and macros throughout the site. So you can’t just look for class and style attributes.

I initially tried with Gemini, but despite feeding it info on Tachyons and handcrafting 3 different cheat-sheets, it would screw up the conversions.

I eventually realized it was also a good opportunity to reconsider all the site’s CSS. Theres a ton Tachyons couldn’t do that Tailwind can. I still had gobs of root CSS styles that were horrific to work around—where you’re scared to change anything because you don’t know what’s going to break elsewhere.

This was much more challenging than using Tailwind for a fresh project. The main difficulty was that I have my own ‘user-generated’ content I need to style: the HTML that my Markdown parser spits out. It makes sense to style this all “globally,” but creating my own variables and overriding them was a bit messy and hard to figure out with Tailwind.

For example, say I want to define a spacing variable that’s the padding figures get. But I want this value to change based on the viewport size. This isn’t a ‘Tailwind-native’ way of thinking about it, but it was a common thing I needed to solve because I had generated content all over the place and it made sense to have a few ‘design tokens’ of my own.

After reading Tailwind’s relevant docs several times, I ended up on some GitHub issue with some secret (maybe?) syntax that taught me to do this:

/* NOTE: theme variables can't have media queries (or be inside media
queries), they are required to be simple top-level values. */

:root {
/* mobile */
--page-padding: 1rem;
--figure-spacing: 2rem;

/* sm+ */
@variant sm {
--page-padding: 2rem;
--figure-spacing: 4rem;
}
}

@theme {
--spacing-figure: var(--figure-spacing);
--spacing-page-padding: var(--page-padding);
}


 then, later on, I can use figure as a size, like my-figure to give top and bottom margins based on this value.

I struggled with whether to make color variables for text and background. I went back and forth on this multiple times. In my previous design, I had CSS variables for --text-color and --lighter-text-color and --lightest-text-color. This was pretty useful, so I thought I wanted something like this, but extended to a Tailwind color range (50, 100, 200, 
, 900, 950).

But everything becomes more challenging once you support two color schemes (light and dark).

The first awkwardness is the wording. For a light scheme, ‘lighter’ does really get lighter, but for a dark scheme it gets darker!

Then, once you pick a neutral word (I forget what I picked, let’s say neutral going forward), it’s awkward because light and dark move in opposite directions, whereas all Tailwind colors go from 50 = lightest to 950 = darkest.

Once you abstract that (say you pick a scale where 1 is the background color and 10 is the content color), you realize you’re tying color combinations to each other. For your base layout, your choices of bg-neutral-1 on text-neutral-10 is no problem. But then say you are de-emphasizing text with text-neutral-6 on a lighter background bg-neutral-3. How does this look? Not just “is there enough contrast” (which is a problem), but like, does it look good? If not, do you tweak the whole color scheme(s)?

Experiencing this made me realize I should just manually write out the text and background colors for each variant (light and dark) everywhere, just as Tailwind prescribes. I initially thought Tailwind was too app-focused, because with highly regular content (e.g., a blog) you want to reach for familiar, known colors, not try to remember whether you should use text-zinc-300 or text-zinc-400 to slightly de-emphasize text. I wanted to just reach for text-lighter or text-lightest. But as I rebuilt more and more of the site, I realized that every single place I was replacing a text-lighter, I actually did have a choice, and it didn’t matter if I picked the same lighter text shade as elsewhere.

Speaking of color schemes, I somewhat regret making a vaporwave color scheme years ago as a quick joke because it created more work now than I’d like to admit. Since I migrated so many more styles to inline, I couldn’t help but add a vaporwave: variant as well whenever I made a dark: one. The vaporwave color scheme now works pretty well throughout the site
 but nobody is ever going to use it.

The biggest ah-ha moment in cleaning up the remaining site-wide CSS was learning that you can use @apply to write your global styles with Tailwind classes. This is really amazing, and I hope they keep this (I saw a rumor somewhere they don’t like @apply and were deprecating it). For example:

@theme {
.markdown-body {
>* {
@apply mx-auto px-page-padding max-w-3xl w-full;
}

p,
ul,
ol
{
@apply my-paragraph;
}

a {
@apply text-link visited:text-link-visited
hover:text-link-hover border-b
border-b-transparent hover:border-b-link-hover;

}

}
}

I ended up reducing site-wide css from ~1300 to ~400 lines, which is amazing. More importantly, the rules are way simpler. Like 150 of the 400 are definitions, and the rest are narrow selectors for a few situations:

One more hack I learned to increase selector specificity: you can just double the class name. (I forget why I needed to do this but it totally solved the problem.)

@layer components {
.table-of-contents.table-of-contents {
...
}
}

quick conversions

This table helped in the many parts I rewrote.

For raw CSS, divide pixels by 4 for the Tailwind unit. (e.g., padding-left: 80px; → pl-20).

size

tachyons tailwind
1 1
2 2
3 4
4 8
5 16
6 32
7 64

font

tachyons tailwind
f1 text-5xl
f2 text-4xl
f3 text-2xl
f4 text-lg
f5 text-base
f6 text-sm
f7 text-xs
f8 text-xxs (custom)

post info


Published Jun 5, 2025
Disclaimer This is an entry in the garage. It may change or disappear at any point.