Moving away from Tailwind, and learning to structure my CSS
Julia Evans
<p>Hello! 8 years ago, I <a href="https://jvns.ca/blog/2018/11/01/tailwind--write-css-without-the-css/">wrote excitedly about discovering Tailwind</a>.</p>
<p>At that time I really had no idea how to structure my CSS code and given the
choice between a pile of complete chaos and Tailwind, I was really happy to choose
Tailwind. It helped me make a lot of tiny sites!</p>
<p>I spent the last week or so migrating a couple of sites away from Tailwind and
towards more semantic HTML + vanilla CSS, and it was SO fun and SO interesting,
so here are some things I learned!</p>
<p>As usual I’m not a full-time frontend developer and so all of my CSS learning
has happened in fits and starts over many years.</p>
<h3 id="it-turns-out-tailwind-taught-me-a-lot">it turns out Tailwind taught me a lot</h3>
<p>When I started thinking about structuring CSS, I was intimidated at first: I’m
not very good at structuring my CSS! But then I started reading blog posts
talking about how to structure CSS (like <a href="https://www.miriamsuzanne.com/2022/09/06/layers/">A whole cascade of layers</a> or <a href="https://jacobb.nyc/writing/how-i-write-css-in-2024">How I write CSS in 2024</a>)
and I realized a couple of things:</p>
<ol>
<li>Every CSS code base has a bunch of different things going on (layouts! fonts! colours! common components!)</li>
<li>It’s extremely useful to have systems or guidelines to manage each of those things, otherwise things descend into chaos</li>
<li>Tailwind has systems for some of these, and I already know those systems! Maybe I can imitate the systems I like!</li>
</ol>
<p>For example, Tailwind has:</p>
<ul>
<li>a reset stylesheet</li>
<li>a <a href="https://jvns.ca/blog/2026/05/04/css-colour-palettes/">colour palette</a></li>
<li>a <a href="https://v2.tailwindcss.com/docs/font-size">font scale</a></li>
</ul>
<h3 id="the-systems-i-m-going-to-talk-about">the systems I’m going to talk about</h3>
<p>I’m going to talk about a few aspects of my CSS codebase and my thoughts so far
what kind of rules I want to impose on the codebase for each one. Some of them
are copied from Tailwind and some aren’t.</p>
<ol>
<li>reset</li>
<li>components</li>
<li>colours</li>
<li>font sizes</li>
<li>utility classes</li>
<li>the base</li>
<li>spacing</li>
<li>responsive design</li>
<li>the build system</li>
</ol>
<h3 id="1-reset">1. reset</h3>
<p>I just copied Tailwind’s “<a href="https://v2.tailwindcss.com/docs/preflight">preflight styles</a>”
by going into <code>tailwind.css</code> and copying the first 200 lines or so.</p>
<p>I noticed that I’ve developed a relationship with Tailwind’s CSS reset over time,
for example Tailwind sets <code>box-sizing: border-box</code> on every element (which means
that an element’s width includes its padding):</p>
<pre><code>* { box-sizing: border-box; }
</code></pre>
<p>I think it would be a real adjustment for me to switch to writing CSS without
these, and I’m sure there are lots of other things in the Tailwind reset (like
<code>html {line-height: 1.5;}</code>) that I’m subconsciously used to and don’t even realize are
there.</p>
<h3 id="2-components">2. components</h3>
<p>This next part is the bulk of the CSS!</p>
<p>The idea here is to organize CSS by “components”, in a way that’s spiritually
related to Vue or React components. (though there might not actually be any Javascript at all in the site)</p>
<p>Basically the idea is that:</p>
<ol>
<li>Each “component” has a unique class</li>
<li>The CSS for one component never overrides the CSS for any other component</li>
<li>Each component has its own CSS file</li>
</ol>
<p>So editing the CSS for one component won’t mysteriously break something in
another component. And probably like 80% of the CSS that I would actually want
to change is in various component files, so if I’m editing a 100-line component,
I just have to think about those 100 lines. It’s way easier for me to think
about.</p>
<p>For example, this HTML might be the <code>.zine</code> “component”.</p>
<pre><code><figure class="zine horizontal">
<img src="whatever.jpg">
</figure>
</code></pre>
<p>And the CSS looks something like this, using nested selectors:</p>
<pre><code>.zine {
...
&.horizontal {
...
}
&.vertical {
...
}
&:hover {
...
}
}
</code></pre>
<p>I haven’t done anything programmatic (like web components or
<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope">@scope</a>)
that ensures that components won’t interfere with each other, but just having a
convention and trying my best already feels like a big improvement.</p>
<p>Next: conventions to maintain some consistency across the site and keep these
components in line with each other!</p>
<h3 id="3-colours">3. colours</h3>
<p><code>colours.css</code> has a bunch of variables like this which I can use as necessary.
Colour is really hard and I didn’t want to revisit my use of colour in this
refactor, so I left this alone.</p>
<p>The only guideline I’m trying to enforce here is that all colours used in the
site are listed in this file.</p>
<pre><code>:root {
--pink: #fea0c2;
--pink-light: #F9B9B9;
--red: #f91a55;
--orange: rgb(222, 117, 31);
...
}
</code></pre>
<h3 id="4-font-sizes">4. font sizes</h3>
<p>One thing I appreciated about Tailwind was that if I wanted to set a font
size, I could just think “hm, I want the text to be big”, write <code>text-lg</code>, and
be done with it! And maybe if it’s not big enough I’d use <code>xl</code> or <code>2xl</code> instead.
No trying to remember whether I’m using <code>em</code> or <code>px</code> or <code>rem</code>.</p>
<p>So I defined a bunch of variables, taken from Tailwind, like this:</p>
<pre><code> --size-xs: 0.75rem;
--line-height-xs: 1rem;
--size-sm: 0.875rem;
--line-height-sm: 1.25rem;
</code></pre>
<p>Then if I want to set a font size, I can do it like this. It’s a little more
verbose than Tailwind but I’m happy with it for now.</p>
<pre><code>h3 {
font-size: var(--size-lg);
line-weight: var(--line-weight-lg);
}
</code></pre>
<h3 id="5-utilities">5. utilities</h3>
<p>There are some things like buttons that appear in many different components.
I’m calling these “utilities”.</p>
<p>I copied some utility classes from Tailwind (like <code>.sr-only</code> for things that
should only appear for screenreader users).</p>
<p>This section is pretty small and I try to be careful about making changes here.</p>
<h3 id="6-the-base">6. the base</h3>
<p>“base” styles are styles that apply across the whole site that I chose myself. I
have to keep this section really small because I’m not confident enough to
enforce a lot of styles across the whole site. These are the only two I feel
okay about right now, and I might change the <code><section></code> one:</p>
<pre><code>/* put a 950px column in the middle of each <section> */
section {
--inner-width: 950px;
padding: 3rem max(1rem, (100% - var(--inner-width))/2);
}
a {
color: var(--orange);
}
</code></pre>
<p>I think for the base styles it’s going to be easiest for me to work kind of
bottom up – first start with almost nothing in the base styles, and then move
some styles from the components into base styles as I identify common things
I want.</p>
<h3 id="7-spacing">7. spacing</h3>
<p>I haven’t completely worked out an approach to managing padding and margins yet.
I’m definitely trying to be more principled than how I was doing it in Tailwind
though, where I would just haphazardly put padding and margins everywhere until
it looked the way I wanted.</p>
<p>Right now I’m working towards making the outer layout components in charge of
spacing as much as possible. For example if I have a <code><section></code> with a bunch of
children that I want to have space between them, I might use this to space the
children evenly:</p>
<pre><code>section > *+* {
margin-top: 1rem;
}
</code></pre>
<p>Some inspiration blog posts:</p>
<ul>
<li><a href="https://piccalil.li/blog/my-favourite-3-lines-of-css/">the owl selector</a></li>
<li><a href="https://kyleshevlin.com/no-outer-margin/">“no outer margin”</a></li>
</ul>
<h3 id="8-responsive-design-use-more-grid">8. responsive design: use more grid!</h3>
<p>The way I was doing responsive design in Tailwind was to use a lot of media
queries. Tailwind has this <code>md:text-xl</code> syntax that means “apply the <code>text-xl</code>
style at sizes <code>md</code> or larger”.</p>
<p>I’m trying something pretty different now, which is to make more flexible CSS
grid layouts that don’t need as many breakpoints. This is hard but it’s really
interesting to learn about what’s possible with grid, and it’s a good example of
something that I don’t think is possible with Tailwind.</p>
<p>For example, I’ve been learning about how to use <code>auto-fit</code> to automatically use
2 columns on a big screen and 1 column on a small screen like this:</p>
<pre><code> display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 400px), max-content));
justify-content: center;
</code></pre>
<p>I also used <a href="https://wizardzines.com/comics/css-grid-areas/"><code>grid-template-areas</code></a> a lot which is an amazing feature that I don’t think you can use with Tailwind.</p>
<p>Some inspiration:</p>
<ul>
<li><a href="https://css-tricks.com/a-responsive-grid-layout-with-no-media-queries/">A responsive grid layout with no media queries</a> from CSS Tricks</li>
</ul>
<h3 id="9-the-build-system-esbuild">9. the build system: esbuild</h3>
<p>In development, I don’t need a build system: CSS now has both built in import statements, like this:</p>
<pre><code>@import "reset.css";
@import "typography.css";
@import "colors.css";
</code></pre>
<p>and built in nested selectors, like this:</p>
<pre><code>.page {
h2 { ...}
}
</code></pre>
<p>If I want, I can use <code>esbuild</code> to bundle the CSS file for production. That looks something like this.</p>
<pre><code>esbuild style.css --bundle --loader:.svg=dataurl --loader:.woff2=file --outfile=/tmp/out.css
</code></pre>
<p>Even though I usually avoid using CSS and JS build systems, I don’t mind using esbuild
(which I <a href="https://jvns.ca/blog/2021/11/15/esbuild-vue/">wrote about in 2021 here</a>)
because it’s based on web standards and because it’s a static Go binary.</p>
<h3 id="why-migrate-away-from-tailwind">why migrate away from Tailwind?</h3>
<p>A few people asked why I was migrating away from Tailwind. A few factors that
contributed are:</p>
<ul>
<li>Tailwind has become much more reliant on a build system since 2018, I think it’s
impossible (?) to use newer versions of Tailwind without using a build system.
So I’ve been using Tailwind v2 for years. (there’s also <a href="https://litewindcss.com/">litewind</a> apparently)</li>
<li>It’s always been true that you’re supposed to use Tailwind with a build
system, but I’ve never really done that, so I have 2.8MB <code>tailwind.min.css</code>
files in a lot of my projects and it feels a little silly.</li>
<li>I’m a lot better at CSS than I was when I started using Tailwind</li>
<li>Ultimately Tailwind is limiting: if you want to do Weird Stuff in your CSS,
it’s not always possible with Tailwind. Those limits can be extremely useful
(a lot of this post is about me reimplementing some of Tailwind’s limits!) but
at this point I’d like to be able to pick and choose.</li>
<li>I ended up with sites that mixed both vanilla CSS and Tailwind in the same
project and that was not fun to maintain</li>
<li>I got curious about what writing more semantic HTML would feel like.</li>
</ul>
<h3 id="css-features-i-m-curious-about">CSS features I’m curious about</h3>
<p>While doing this I learned about a lot of CSS features that I didn’t use but am
curious about learning about one day:</p>
<ul>
<li><code>@layer</code> (from <a href="https://www.miriamsuzanne.com/2022/09/06/layers/">A Whole Cascade of Layers</a>)</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@scope">@scope</a>)</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Containment/Container_queries">container queries</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout/Subgrid">subgrid</a></li>
</ul>
<h3 id="that-s-all-for-now">that’s all for now!</h3>
<p>I still feel happy that I started using Tailwind, even if I’m moving away from
it now. I learned a lot from using it and I can still use some parts from it in
my sites even after deleting <code>tailwind.min.css</code>.</p>
<p>Thanks to <a href="https://melody.dev/">Melody Starling</a> who originally designed and wrote the CSS for
<a href="https://wizardzines.com">wizardzines.com</a>, everything cool and fun
about the site is thanks to Melody.</p>
<p>Also I read so many incredible blog posts about CSS while working on this (from <a href="https://css-tricks.com/">CSS Tricks</a>, <a href="https://www.smashingmagazine.com/">Smashing Magazine</a>, and more), I’ve tried to link some of them
throughout this post and I really appreciate how much folks in the CSS community share their practices.</p>