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
andstyle
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:
- the markdown body plus all potential child elements (this is the messiest part)
- the metadata table at the end of posts
- table of contents (appears 3 times)
- a couple one-off utils
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) |