As a developer relying on TailwindCSS for crafting responsive designs effortlessly, the release of TailwindCSS v4 was an exciting occurrence. I jumped immediately into the project I’m currently working on, and started upgrading to the latest Tailwind version.
We use a somewhat simple approach to TailwindCSS in our Umbraco (ASP.net MVC) projects. In the website project, we initalize an npm package with npm init
and then install TailwindCSS and necessary scripts for building. The source files is placed in ./UI/CSS/main.css
and the config in ./UI/CSS/tailwind.config.js
Moving config to CSS, spacing changed
One of the big headlines of the new Tailwind version, is the addition of config in CSS. With that, all configurable values are now defined as custom properties, which adds some exciting new possibilities.
I like to stay as close to the default config as possible, so moving this over to CSS was not a big hurdle.
Though, I used to generate spacing values using an npm package I built, generate-tailwind-scale, I opted to leave this one behind, as the new spacing scale is dynamic.
I did use the same package for defining font-sizes though, but for this project, most of the font-sizes used already matched the default font-sizes. And the rest I simply changed into using arbitrary values, eg. text-[10px]
Automatic content scanning
In TailwindCSS v3, developers had to manually specify content paths to ensure Tailwind scanned the correct files for class usage. TailwindCSS v4 automates this process, scanning the entire directory it's executed from (except files specified in .gitignore
). This clever tweak reduces configuration overhead.
Initially, I was worried that my output.css
file would be considered for class generation since we don’t git-ignore it in our projects. If it were considered, removing a class would be impossible. Fortunately, it's not included, and when I added a one-time class like m-[1337ch]
, it appeared in the output but was promptly removed when unused elsewhere.
For content files ignored by .gitignore or located outside the project path, developers can specify paths via @source "../path/to/something"
, offering flexibility for more complex directory structures.
No more content transforms (for now?)
One missing feature in this content definition method is the ability to transform content during scanning. Previously, I configured content to allow writing @@container
to add the @container
class used by the container query plugin (now built into Tailwind!). Since my frontend is written in Razor/cshtml, @container
would cause errors, while @@
worked as an escape character. For now, I have to wrap those like @("@container")
.
UTF-8 BOM encoding issue when working from Visual Studio
A notable challenge during the upgrade was related to file encoding. If your main Tailwind input file (main.css
) was created using Visual Studio, it might be saved in UTF-8 BOM format. TailwindCSS v4 cannot correctly process UTF-8 BOM, causing incorrect or incomplete outputs.
This issue initially puzzled me, as the output.css
file still contained Tailwind-specific directives like @utility
and @import "tailwindcss"
.
The simple solution is to save your CSS file as UTF-8 without BOM. Once converted, TailwindCSS functions as expected.
Safelisting classes is deprecated
In a significant change, TailwindCSS v4 no longer supports class safelisting. For simple classes, I recommend documenting them in comments within your CSS files. I previously used regex for safelisted classes but had to refactor. Instead of using direct dynamic class definitions like class="bg-@Model.Color"
, I transitioned to custom properties: class="bg-(--my-bg)" style="--my-bg: var(--color-@Model.Color)"
. This approach also results in a cleaner, more maintainable solution, and fewer bg-
and color-
classes.
New method for custom utilities and variants
Another significant change is the method for defining custom utilities. The reliance on @layer utilities
blocks is gone. Instead, in TailwindCSS v4, you can define them using a new directive: @utility
. For example, to create a utility to hide scrollbars:
@utility scrollbar-hidden {
/* css here */
}
For variants it has become very simple, heres an example of a custom variant I had in this project:
@custom-variant mouse-only (@media screen and (pointer: fine));
Simpler plugin setup
TailwindCSS v4 introduces a more streamlined way to incorporate plugins. Plugins are now embedded directly using directives such as @plugin "@tailwindcss/typography"
. This approach simplifies the integration process, reducing boilerplate code and setup time.
And it seems like v3 plugins are supported out of the box. At least my quantity query plugin just works.
JavaScript config still possible
While TailwindCSS v4 still supports JavaScript configuration files, the authors advises against using them. I think they will end up completely deprecated at some time, but at least not before an eventual v5 or v6 of TailwindCSS.
Conclusion
Updating to TailwindCSS v4 was quite straight forward. If not for the BOM encoding issue, I would say it was as easy as pie.
Next, I think I need to link into using the standalone CLI in the build pipeline, or maybe even when starting the website, to make our projects even simpler to work with.
Bonus tip
Working with TailwindCSS is a lot better, with the right tools. For VSCode, Tailwind Labs has their own intellisense extension.
For full Visual Studio, an unofficial extension offers similar functionality. Although it's not supporting v4 yet, the author is trying to make it happen in the next few weeks.
Top comments (1)
I’ve been using Tailwind primarily on frontend-only projects, and I would love an official Visual Studio extension that fully supports Razor views. I haven’t yet tried integrating Tailwind into a .NET project, so if you happen to have a demo or a sample repository showing how you set everything up, I’d be thrilled to take a look. Thanks again for all the helpful details!