Skip to content

Release Notes

January 2025

Upgrading to Tailwind 4 and all that it entails (with a bonus ESLint upgrade!)

From the first Twitter posts with plans for Tailwind 4, it was obvious it would be a more significant upgrade than previous major versions. Its push to simplify the configuration process has greatly reduced the number of packages _tw requires to function, and it also led me to rethink a number of assumptions about the decisions I should be making on behalf of _tw users.

Entering the Tailwind 4 era

I can’t even begin to outline everything that has changed in Tailwind, so I strongly encourage you to read the blog post announcing the release.

If I had to give the briefest of summaries, I would say that it is incredibly fast, easier to configure and modernized in a bunch of important ways. (P3 colors! Container queries! So much else!)

It also encourages its users to go all in with the Tailwind approach, removing previous support for things like Sass-style nesting. (CSS-native nesting is now supported instead by default, with no first-party approach to supporting the old style.)

And this all meant a lot of changes to _tw!

Supporting theme.json variables in Tailwind 4

Earlier versions of _tw used a custom Tailwind plugin to parse the theme’s theme.json file and to make available its color palette and content sizing values.

This also required another watch process tracking changes to theme.json and triggering a Tailwind rebuild when it changed.

This approach both doesn’t work well (or at all) with Tailwind 4 and is made unnecessary by the switch to configuration variables.

You can still set your colors in theme.json. The change is that now you’ll see them listed as design tokens for Tailwind in ./tailwind/tailwind-theme.css:

--color-background: var(--wp--preset--color--background);
--color-foreground: var(--wp--preset--color--foreground);
--color-primary: var(--wp--preset--color--primary);
--color-secondary: var(--wp--preset--color--secondary);
--color-tertiary: var(--wp--preset--color--tertiary);
--container-content: var(--wp--style--global--content-size);
--container-wide: var(--wp--style--global--wide-size);Code language: CSS (css)

This adds the design tokens necessary for classes like bg-background and text-foreground, and it assigns them the variables created by WordPress from the values in the theme.json file.

Removing PostCSS

Tailwind no longer supports running PostCSS via its CLI, and there weren’t enough PostCSS features used by _tw to justify a PostCSS workflow.

(I strongly prefer to use the Tailwind CLI directly, which used to support the --postcss flag. That flag has been removed.)

Because Tailwind 4 adds functionality equivalent to postcss-import and renders that plugin unnecessary, and because Tailwind 4 removes tailwindcss/nesting, the only remaining PostCSS plugin _tw used to use was postcss-import-ext-glob. This allowed for files in the ./tailwind/custom/components folder to be automatically imported; this is a nice feature, but in my opinion doesn’t warrant switching from the Tailwind CLI to using PostCSS directly.

Removing Tailwind’s first-party plugins

I used to include all of Tailwind’s first-party plugins for convenience, allowing them to be activated simply by uncommenting them. Only three commented-out first-party plugins remained prior to Tailwind 4’s release. Of those, two are now deprecated:

  • @tailwindcss/aspect-ratio
  • @tailwindcss/container-queries

In the case of the former, it supports legacy approaches to aspect ratios in older browsers, but Tailwind 4 itself targets newer browsers than the first to natively support CSS aspect ratios. The latter has been totally subsumed by Tailwind’s core functionality.

That leaves only @tailwindcss/forms, and I’m not convinced it makes sense to deliver that to all users of _tw by default, so I have removed it.

Tailwind class name ordering in PHP files

Because the first-party Prettier plugin for Tailwind class sorting doesn’t support PHP files in WordPress templates (because Prettier’s PHP plugin doesn’t support inline PHP), _tw used a somewhat complicated ESLint pipeline that would parse the PHP files with @angular-eslint/template-parser after removing the PHP with eslint-plugin-php-markup. Then the third-party ESLint plugin for Tailwind—eslint-plugin-tailwindcss—would handle class sorting.

Unfortunately, that last plugin has not been updated for Tailwind 4, and I didn’t want to hold back Tailwind 4 support in _tw waiting for that update.

In order to support class name ordering in PHP files once again, we’ll either need the above ESLint plugin to be updated for Tailwind 4, or for Prettier’s support for inline PHP to stabilize.

ESLint 9, finally

I have received zero complaints from users about the long wait for ESLint 9 support, but that doesn’t mean I’m not excited to push it to production!

If you haven’t been following along, ESLint 9 deprecated the old approach to configuration via .eslintrc files, replacing it with flat configuration in an eslint.config.mjs file.

The upgrade also broke Tailwind class name ordering in PHP files because one of the ESLint plugins _tw relied upon hadn’t been updated to support ESLint 9.

I had already learned the new flat config format and forked the ESLint plugin, rewriting the parts of it we needed to support ESLint 9.

However, with Tailwind 4 eliminating the possibility of Tailwind class name ordering in PHP files for the time being, I was able to focus on supporting ESLint for the basic purpose of linting JavaScript files.

I have also come around to the flat configuration format, and I think it’s an improvement over the old way. Exciting times!


This update is the biggest change to _tw since it launched, so if you have any comments, I’d really love to hear them! I’ve joined Bluesky, and you can find me there as @gregsullivan.com.

I’m excited to start seeing _tw-powered Tailwind 4 sites in the wild, and I look forward to seeing what everyone builds!