
Moving from Tailwind 3 to Tailwind 4
Tailwind 4 was released in January 2025. It is a major version update with a lot of enhancements and also changes - some breaking. In this article we will share our experience setting up and configuring Tailwind 4 in our brand new Next.js v15 Github project template.
Tailwind 4 was released in January 2025. It is a major version update with a lot of enhancements and also changes - some breaking. Along with the performance improvements there are tons of added support for newer CSS rules and APIs which we all enjoy.
However, there are also some setup changes that need to be considered before making the move towards Tailwind 4 such as a CSS-first configuration and also support for newer CSS rules means there is a pegged version for browser dependency. Hence if you have an existing project in version 3 or a project whose users are on older browser versions, you will have to consider the repercussions of moving to the latest version.
Tailwind is our team’s goto styling framework for our projects due to its performance benefits and its utility-first paradigm. In this article we will share our experience setting up and configuring Tailwind 4 in our brand new Next.js v15 Github project template. You will get a glimpse of the thought process that goes into how we architect our project code base and folder structure to ease organization of files. This helps simplify the mental model of figuring out where to put styles and reducing cognitive load of our developers.
Setup Considerations
As mentioned earlier, Tailwind 4 has some browser requirements to take into consideration before using it in a project. If your users are on evergreen browser versions then this is typically not a problem. However in a corporate or B2B landscape, your site visitors might have a certain browser version dictated by corporate IT policies which can make things difficult to use bleeding-edge platform features.
According to Tailwind’s compatibility page, the core functionalities of the new version is built to support features found in these browser versions:
Chrome 111 (March 2023 release)
Safari 16.4 (March 2023 release)
Firefox 128 (July 2024 release)
Another key thing to decide on is the new CSS-first configuration. In versions up to Tailwind V3, all configurations of Tailwind can be in a tailwind.config.ts/js file. This file is where you would tell Tailwind what files or folders to scan for Tailwind styles, extend or replace default Tailwind styles, define custom plugins, disable certain core plugins, etc.
In Tailwind 4, all of these configurations are moved into your CSS file and done through new Tailwind directives such as @theme for defining custom design tokens, and some functionalities such as disabling unused core plugins are no longer supported. This shift bears a significant change in way of doing things and what is possible in Tailwind 4.
Our developers at 9thCO love embracing newer ways of doing things and have decided to go all-in on V4. Since the majority of our projects are in the Javascript ecosystem and with Single Page Apps (SPAs), JS configuration files are a standard go-to place for our team to make changes. Moving configuration into a CSS file and reliance on custom Tailwind directives and APIs does feel counterintuitive and even feels a bit more vendor-locking.
Setting up Tailwind V4
Adding Tailwind V4 to a Next.js project is pretty straightforward. On a typical Next.js project, Tailwind is configured through PostCSS. The basic installation instructions can be found here. Once it's been added to a project, this is where different teams will introduce their flavor of setting up Tailwind for use in their project. At 9thCO, our styling setup and workflow is heavily influenced by what works best in other precompiler tools such as SASS.
File and folder structure
When working with a utility-first styling framework such as Tailwind, developers should reach for pre-existing utility classes first before creating custom CSS classes. Using Tailwind utility classes quickly surfaces repeated stylings in different components. This encourages front-end developers to apply DRY principle and create only reusable custom CSS classes and unique one-off exceptions in very rare situations. Hence when using Tailwind, the amount of custom CSS, length and number of CSS files is much reduced - typically most simple projects have only one CSS file.
At 9thCO, we adopt an SASS partials approach to Tailwind. In SASS, it is common to break down CSS files into smaller sub CSS files called partials by their responsibilities or styling areas and then reassemble them in a main entry file. The precompiler will read the entry file and parse through all the partials to compile all the SASS styling into one final CSS file.
The benefit of this pattern is that it forces developers to think about what custom classes they are creating, whether it fits into the utility-first paradigm, and where it fits among the defined styling categories/layers (base, component, or utility classes). These partial files are then loaded into the main entry file using the @import directive.
In our project folder, we have a styles
folder in the root directory. In it we have the main entry file named entry.css
where we load Tailwind alongside all the other partial files that are used. The entry file also serves as the main configuration place for Tailwind:
In the screenshot above you can see we have four additional CSS files each taking care of different areas of styling responsibilities.
globals.css
- This file will contain style overrides for third party vendors and libraries such as carousel libraries. Notice the placement of it comes immediately after Tailwind import. This allows us to reference Tailwind theme values inside of vendor class overrides. This also allows later CSS files to have a higher precedence and possible to override these styles if needed without the use of!important
tw-base.css
- This file contains base HTML element styling. Using the@layer base
directive, the CSS styles are added to the end of Tailwind’s base styles allowing us to override default Tailwind base styles that come before it.tw-component.css
- This file contains reusable custom CSS classes that each defines multiple styling rules to accomplish a specific design. For example, a CSS class to achieve a custom card component look (padding, margin, border-radius, box-shadow, etc.) or a convenience custom CSS class to achieve a particular layout for every section of a page (max-width, padding, margin). It is easy to confuse the use of component classes vs utility classes. Utility classes are meant to perform typically a single responsibility and are much smaller in scope that it can be applied almost anywhere to accomplish its goal, e.g..px-4
applies a horizontal padding to the element that it’s attached to. Styles inside of the@layer components
are added to the end of Tailwind’s component styles allowing us to override default Tailwind component styles that come before it. There is a breaking change in V4 where variants do not work with custom defined component CSS classes. It does seem like component classes have become second-class citizens in V4 compared to utility classes.tw-utilities.css
- This file contains custom utility styles defined in the project. In Tailwind V3, utilities are added to Tailwind’s own utilities via@layer utilities
. This involved "hijacking" the @layer at-rule to inject those styles at a specific point in the CSS. However in Tailwind V4, the recommended approach to adding custom utilities is via the@utility
directive that uses native cascade layers to organize their CSS. Compared to the@layer
directive that wraps the entire chunk of classes, the@utilities
directive is added each time a new CSS class is defined. You also do not add a period in front of the CSS class name which is counter intuitive to the traditional way of writing CSS classes. We are not entirely excited about this new artificial syntax introduced here:
Making your Developer Experience Better
Tailwind’s custom directives warning
It is very useful to have syntax highlighting in development. With Tailwind, there is an official Tailwind CSS IntellisSense extension available in most common IDE such as Visual Studio Code or Cursor. This helps highlight styles, provide useful information about Tailwind directives, and autocomplete options.
However, there is still some configurations needed even after you install the IntelliSense extension. By default, your IDE does not know how to differentiate between a vanilla CSS file VS. one that uses Tailwind and its synthetic directives. You will notice a warning for all the custom Tailwind directives in your CSS file:
To force your IDE to recognize this difference, in VS Code, you will need to add the following to the settings.json
file:
The above tells VS Code to treat all CSS files as tailwind files with the exception of module CSS files - treating those as regular CSS files.
Bonus: Talk about @reference
Adding Prettier
To make your styling job a breeze, there is also an official Prettier plugin package that help sort and group CSS classes as you add them to your markup. When upgrading to V4, it is important to also define your entry point file in the .prettierrc
configuration file:
Prefix classes in Tailwind V4
Our team prefers to prefix our Tailwind utility classes to avoid possible namespace clashing if other vendor libraries with the same class names but different CSS rules were introduced to the codebase.
In Tailwind V3, to set a prefix is as easy as adding a prefix key to the config object inside of tailwind.config.ts like so:
Notice the hyphen in the prefix. Prefixed, CSS classes are then used like so: u-px-4 In Tailwind V4, the way to add a prefix is on the same line where you import Tailwind in the entry CSS file like so:
In Tailwind V4, prefixes can no longer have any special characters after the letters in your prefix. Instead, prefixes uses Tailwind’s variant syntax e.g. u:px-4. In V3, prefixes appear after all other Tailwind variant prefixes e.g. lg:u-px-4
. But in V4, the prefix comes before all other Tailwind variant prefixes e.g. u:lg:hover:u-px-4
We like this better since it is easier to spot and apply prefixes.
Legacy Support
As mentioned in the beginning of this article, Tailwind V4 has moved towards a CSS-first configuration approach. However, this doesn’t mean the option to support legacy JS configuration files are gone. If you are seeking to migrate from Tailwind V3 to V4, the Tailwind team has an upgrade tool that can be found here: https://tailwindcss.com/docs/upgrade-guide
The migration will add your config file path in the entry CSS file using the @config
directive. Here is some documentation around Tailwind directives that exist to support legacy V3 configurations. A quick note is that the ability to create custom plugins in V4 is still in development (while there is a way to load V3 plugins using the @plugin
directive and ability to read theme values using the theme()
has been deprecated).
Happy theming in Tailwind V4!