Configuring Tailwind 4.0
Published about 2 years ago
Okay, someone had to do it—I'm putting all the teases about the Tailwind 4.0 configuration DX in one place. Also sharing my unsolicited opinions about them. Will keep this updated as we learn more!
The basic idea is that we'll configure Tailwind 4.0 in a CSS file, using @layer
, custom at-rules, and valid-but-technically-meaningless CSS syntax to replace (almost) all the features we know and love in our tailwind.config.js
files.
JS config files will still be supported. For more niche Tailwind use cases, there are things you can do with a JS config that just aren't practical or possible with a custom CSS API.
Every so often, Adam Wathan tweets out cool ideas about this idea. Let's take a look at some!
Theme
Code:
@import "tailwindcss";
@theme {
--colors-white: #fff;
--colors-black: #000;
--colors-gray-1: ...;
--colors-gray-2: ...;
--colors-gray-3: ...;
...
}
@theme extend {
--font-family-display: Poppins, sans-serif;
}
This syntax for theme configuration looks pretty cool, but still a little rough around the edges. We'd get great syntax highlighting and autocomplete for design tokens (way better than just throwing strings around in a JS file), but the repeated --color-
prefix is verbose and annoying.
Based on another tweet, though, I think the Tailwind team has a better solution in mind:
@theme {
--colors: {
--white: #fff;
--black: #000;
--gray: {
--1: ...;
--2: ...;
--3: ...;
};
};
...
}
This is actually valid CSS variable syntax—there's no fancy nesting going on here. If you wrote that --colors
value for a variable in plain CSS, the browser would have no trouble parsing it:

The browser can parse nested CSS variable blocks, and even detect colors
The rule is technically valid, it just has no effect on the page.
You can even see in the code snippet that syntax highlighting just works—I didn't do anything special to get proper support.
The implication for Tailwind is that we can use nested values to reduce repetition in userland code, and the Tailwind team doesn't have to bend over backwards to build linters, syntax highlighters, language support, and all the other headaches that come with custom, invalid syntaxes.
More importantly, since this syntax has zero effect on plain CSS styles, there's little to no risk that anyone would ever use it for a native feature, and no risk that Tailwind would have to backtrack and break everyone's config files.
Plugins
Code:
// tailwind.css
@layer utilities {
.tab-(@size: theme(tab-size)) {
tab-size: @size;
@type length;
}
}
That code snippet is a sketch of a possible alternative to Tailwind's JS plugin API. Here's the equivalent code in the current API:
// tailwind.config.js
const plugin = require('tailwindcss/plugin')
module.exports = {
// ...
plugins: [
plugin(({ matchUtilities, theme }) => {
matchUtilities(
{
tab: value => ({ 'tab-size': value })
},
{
value: theme('tabSize'),
type: ['length']
}
)
})
]
}
From my experience writing plugins, I'm less convinced that I'll use custom CSS syntax instead of JS.
The logic of the plugins I've written is pretty straightforward. A handful of if
statements, a for
loop or two, maybe even a little regex replacement—as JS goes, it's not very complicated.
But if you want to write that in CSS, it would take a lot of work behind the scenes. Tailwind might come up with custom syntax to support basic JS features, or they might depend on a curated collection of PostCSS plugins, or they might just allow plugin authors to register our own PostCSS plugins and dependencies for whatever custom syntax we need.
For something like @tailwindcss/typography
, though, custom CSS syntax feels completely off the table. Internally, @tailwindcss/typography
recurses through a tree of nested CSS-in-JS, wrapping each nested CSS selector in a complicated :where()
statement (to keep styles easy to override, and to support the awesome .not-prose
feature).
As cool as PostCSS is, it's not a good language for defining recursive functions.
And that's fine, because there's been no indication that the JS plugin API is getting removed. It's more likely that we'll get a cool CSS-based plugin API for the 80% use case, and the JS API will cover the more complex 20%.