Upgrading to Tailwind CSS v4: A Practical Guide

Upgrading to Tailwind CSS v4: A Practical Guide

At Villa Plus, we’re committed to modernising our technology stack to enhance developer productivity and user experience. As part of this strategic initiative, we’ve implemented modern frameworks and libraries with custom architectures to streamline our UI development pipeline.

So let us dive into today’s topic, you’ve heard about Tailwind CSS v4 and all its shiny new features. Before you dive headfirst into upgrading, let’s walk through the process with real-world experience. We recently upgraded our Svelte v4 project to Tailwind v4, and I’m sharing the complete journey — including the gotchas, browser compatibility issues, and a bonus fix for Safari users.

Before You Even Consider Upgrading

Check your user analytics first! This is crucial. Tailwind v4 relies on modern CSS features like the @layer at-rule, @oklch color format, and range media queries that aren't supported in older browsers.

Initially, we found that a portion of our users were on older Safari browsers, which could have derailed our upgrade plans. However, instead of abandoning the upgrade, we created a simple script to address these compatibility issues. This approach allowed us to proceed with the upgrade while minimizing the impact on our users (Provided the script in the Bonus section ??).

Starting the Upgrade Process

We’ve performed this upgrade in a Svelte 4 project, but the steps outlined here are generally applicable across most frameworks.

First, create a new branch for your upgrade work. The actual upgrade process is straightforward, thanks to Tailwind’s migration tool:

pnpm dlx @tailwindcss/upgrade        

This script handles most of the migration, here are the major syntax changes you’ll encounter:

if you are using prefix, we may see some notable syntax changes like below:

  • Prefix syntax: vp- becomes vp:
  • Breakpoints order: md:vp- is now vp:md:
  • Negative values: -vp-mt-1 is now vp:-mt-1

if you are not using prefix then these are the most notable syntax changes:

  • Important modifier: !h-10 is now h-10!
  • CSS Variable syntax: mr-[var(--header-margin-right,0px)] is now mr-(--header-margin-right,0px)

For a comprehensive list of changes, check the official Tailwind v4 upgrade guide.

The Inevitable Broken Pieces

After running the upgrade script, you’ll likely find some broken parts in your application. Here are the three main issues we encountered:

1. Preflight Changes

If you’re using Tailwind’s preflight (their version of CSS reset), you might notice issues with images. By default, Tailwind v4 sets:

img {
  max-width: 100%;
  height: auto;
}        

This caused problems in Firefox for us, and you may encounter similar issues with styling for base HTML tags. We had to add some custom styling to fix these inconsistencies.

2. Component Layer vs Utility Classes

If you’re using a prefix and have custom component classes, you’ll need to make manual adjustments. The automatic migration script converts components to utilities, which means you need to add your prefix manually.

For example, we had something like this before the upgrade:

@layer components {
   .modal {
     position: fixed;
     top: 0;
     right: 0;
     bottom: 0;
     left: 0;
     z-index: 1050;
     display: none;
     overflow: hidden;
     outline: 0;
   }
}        

But after the automatic upgrade using the CLI, it became:

@utility modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1050;
  display: none;
  overflow: hidden;
  outline: 0;
}        

You have two options here:

  1. Move it back under the @component layer, which we found to be the cleaner approach
  2. Keep it as a utility but add your prefix to all usages in your components, as the automatic script won’t do that for you

3. Space Between Utilities Behavior Changes

According to Tailwind’s v4 documentation,

The space utilities are really just a shortcut for adding margin to all-but-the-last-item in a group, and aren’t designed to handle complex cases like grids, layouts that wrap, or situations where the children are rendered in a complex custom order rather than their natural DOM order. For those situations, it’s better to use the gap utilities when possible, or add margin to every element with a matching negative margin on the parent.

We encountered several layout inconsistencies during our upgrade, so be sure to thoroughly test any components using space utilities to ensure they’re rendering correctly with the new implementation.

4. Svelte-Specific Issues

If you’re using class directives in Svelte, the automated script won’t convert those. You’ll need to manually update code like:

<div class="skeleton" class:vp-hidden={hidden}></div>        

to:

<div class="skeleton" class:vp:hidden={hidden}></div>        

If you are using prefix in tailwind this applies to all syntax changes, including breakpoints swap with prefix, negative signs, and important modifiers. But if you are not using prefix, then you only need to worry about important modifiers.

Additional Configuration Checks

  1. If you’re using a prefix, ensure it’s properly set in your main CSS file:

@import 'tailwindcss' prefix(vp);        

2. Tailwind v4 comes with automatic content detection. If you need to include files outside your project directory, use the @source directive:

@source '../Views/**/*.cshtml';        

3. If you’re using Tailwind CLI for your build, update your script from:

pnpm dlx tailwindcss -i src/styles/main.css -o ../wwwroot/css/core.css --minify        

to:

pnpm dlx @tailwindcss/cli -i src/styles/main.css -o ../wwwroot/css/core.css --minify        

Bonus: Extending Safari Compatibility

One of the biggest challenges we faced was supporting Safari versions 15.4 to 16.3. Without intervention, our site completely broke on these versions because they don’t support range media queries.

We created a script that converts range media queries syntax to traditional media query syntax (only taking 4ms). With this script, we were able to support Safari 15.4+ with minimal issues.

The below script is designed for when you use Tailwind CLI for building the final output. If you’re using Vite as your build tool, you can create a Vite plugin with apply: "build", enforce: "post" configuration to manipulate the CSS file after Tailwind has processed it. In that time the code may look different, but the core convertRangeMediaQueries function will be same.

import { fileURLToPath } from "url";
import { dirname, join } from "path";
import fs from "fs/promises";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const cssFilePath = join(__dirname, "css output path");

function convertRangeMediaQueries(cssContent) {
 const greaterThanOrEqualPattern =
  /@media\s*\(\s*width\s*>=\s*([0-9]+)(px|em|rem|vh|vw|%)\s*\)/g;
 const lessThanOrEqualPattern =
  /@media\s*\(\s*width\s*<=\s*([0-9]+)(px|em|rem|vh|vw|%)\s*\)/g;

 let updatedCss = cssContent.replace(
  greaterThanOrEqualPattern,
  (_, value, unit) => `@media (min-width: ${value}${unit})`
 );

 updatedCss = updatedCss.replace(
  lessThanOrEqualPattern,
  (_, value, unit) => `@media (max-width: ${value}${unit})`
 );

 const rangePattern =
  /@media\s*\(\s*([0-9]+)(px|em|rem|vh|vw|%)\s*<=\s*width\s*<=\s*([0-9]+)(px|em|rem|vh|vw|%)\s*\)/g;
 updatedCss = updatedCss.replace(
  rangePattern,
  (_, minValue, minUnit, maxValue, maxUnit) =>
   `@media (min-width: ${minValue}${minUnit}) and (max-width: ${maxValue}${maxUnit})`
 );

 return updatedCss;
}

async function main() {
 try {
  let totalStartTime = Date.now();
  const data = await fs.readFile(cssFilePath, "utf8");

  const updatedCss = convertRangeMediaQueries(data);

  await fs.writeFile(cssFilePath, updatedCss, "utf8");

  const totalEndTime = Date.now();
  const totalDuration = totalEndTime - totalStartTime;

  console.log(
   `Converted range media queries to normal media queries in ${totalDuration} ms`
  );
 } catch (err) {
  console.error("Error converting range media queries:", err);
 }
}

main();        

And then add this in your tailwind build script like this

pnpm dlx @tailwindcss/cli -i src/styles/main.css -o ../wwwroot/css/core.css --minify && node scripts/tailwind-range-media-query-converter/index.js        

Conclusion

Upgrading to Tailwind v4 brings modern CSS features and improved performance, but it requires careful planning and testing. By following this guide and using our Safari compatibility script, you can make the transition smoother.

Remember, the most important step is checking your user analytics before deciding to upgrade. Modern features are great, but not at the expense of excluding a significant portion of your users.

Test, test, test… before going to production. Make sure to thoroughly check all components and pages across different browsers and devices to catch any unexpected layout or styling issues that might have been introduced during the upgrade.

Have you upgraded to Tailwind v4 yet? What challenges did you face? Or Did you find this article helpful? Let me know in the comments!

Author Sajeesh Kumar A

Sonal Antony

Operations Coordinator @ STEM-Xpert, Techgenius LLP

6 天前

Fascinating Sajeesh Kumar A

要查看或添加评论,请登录

Villa Plus的更多文章

社区洞察