Tailwind CSS is the utility-class framework: instead of writing CSS rules, you put classes like p-4 and text-lg on elements. Version 4.0 was tagged on 21 January, and the announcement went out the next day. It changes how you install and configure Tailwind more than it changes the classes you type.
For a new Vue project, the setup is now two packages, one plugin in the Vite config and one line of CSS, with no config file. For a v3 project, check browser support first, then run the upgrade tool and read its diff.
What shipped in 4.0
The announcement leads with a new engine: full builds up to 5x faster and incremental builds over 100x faster. The median times behind those claims, measured on the team's own Catalyst project, are more useful:
- Full build: 378ms in v3.4, 100ms in v4.0.
- Rebuild that adds new CSS: 44ms to 5ms.
- Rebuild with no new CSS: 35ms to 192µs.
The 100x figure is that last case: a save that only uses classes you have already used finishes in microseconds.
Four changes affect setup, and each has a section below:
- A dedicated Vite plugin,
@tailwindcss/vite. - One
@importline instead of three directives. - Configuration in CSS with
@theme, instead oftailwind.config.js. - Automatic detection of template files, with no
contentarray.
Underneath, the engine is built on modern CSS: native cascade layers, logical properties, custom properties registered with @property (CSS variables with a declared type) and color-mix(), which v4 uses for opacity modifiers like bg-blue-500/50. Those last two set the browser floor in the next section.
New in the classes you write:
- The default palette moves from
rgbtooklch, using the wider P3 colour range of modern screens for more vivid colours. - Container queries are in core, and there are 3D transform utilities.
- Linear gradients take angles (
bg-linear-45), and radial and conic gradients are new. - A
starting:variant transitions an element when it first appears, and anot-*variant applies a style when another variant does not match.
Check browser support before you switch
The compatibility page says the core of v4 depends on Chrome 111, Safari 16.4 and Firefox 128. Those are the first versions that support both @property and color-mix(), and v4.0 has no fallback mode for anything older.
Chrome 111 and Safari 16.4 came out in March 2023, Firefox 128 in July 2024, so for most apps built in 2025 those versions are fine. If your users include older corporate desktops or old iPads that cannot update Safari, stay on v3.4 for now: your v3 setup keeps working as it is.
Some utilities, such as field-sizing and the starting: variant, rely on features with limited browser support. Leave those out if the browsers you support lack them.
Install the Vite plugin
Start from a project created with npm create vue@latest, or any Vue and Vite project that does not use Tailwind yet. Install two packages:
npm install tailwindcss @tailwindcss/vite
Then add the plugin to the Vite config. A create-vue project already has vue() and vueDevTools() there, so the new lines are the tailwindcss import and tailwindcss() at the end of plugins:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [vue(), vueDevTools(), tailwindcss()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
There is no PostCSS config to create and no tailwind.config.js. The announcement says this plugin performs better than the PostCSS one.
Replace main.css with one import
A create-vue project imports src/assets/main.css from main.ts. Replace everything in that file with a single line:
@import "tailwindcss";
In v3 this was three lines: @tailwind base;, @tailwind components; and @tailwind utilities;. In v4 it is a regular CSS @import, and Tailwind takes over two jobs that used to need extra PostCSS plugins:
- It bundles any further
@importrules itself. In v3 that neededpostcss-import. - It uses Lightning CSS underneath for vendor prefixes and nesting.
The new main.css no longer imports the base.css that create-vue ships, so you can delete that file. Run npm run dev and the utilities work in every component.
Add design tokens with @theme
Design tokens, the named values a project agrees on for colours, fonts and spacing, used to live in a JavaScript object. In v4 they are CSS variables inside an @theme block, in the same file:
@import "tailwindcss";
@theme {
--font-display: "Satoshi", sans-serif;
--color-brand-500: oklch(0.62 0.17 250);
--color-brand-700: oklch(0.45 0.15 250);
}
The oklch() numbers are lightness, chroma (how vivid) and hue. sans-serif is the fallback until you load the Satoshi web font, and it has no quotes on purpose: quoted, it would name a font called "sans-serif".
Each variable creates utilities. --color-brand-500 gives you bg-brand-500, text-brand-500, border-brand-500 and every other colour utility. --font-display gives you font-display. The announcement shows the same pattern for breakpoints (--breakpoint-3xl: 1920px) and easing curves.
Tailwind also writes the variables into the built CSS as ordinary custom properties on :root, so var(--color-brand-500) works in a component's <style> block or an inline style.
Use the token in a component
A pricing card with a heading, some text and a button, using the tokens from above:
<script setup lang="ts">
defineProps<{
name: string
price: number
}>()
</script>
<template>
<article class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
<h2 class="font-display text-xl font-semibold text-slate-900">{{ name }}</h2>
<p class="mt-2 text-slate-600">{{ price }} a month, cancel any time.</p>
<button
type="button"
class="mt-4 rounded-lg bg-brand-500 px-4 py-2 text-white hover:bg-brand-700"
>
Choose {{ name }}
</button>
</article>
</template>
Import it into App.vue and render <PlanCard name="Pro" :price="12" />. If the button is blue, the @theme block is working: bg-brand-500 exists only because of it.
border-slate-200 is written out on purpose. In v4 the default border colour is currentColor, not gray-200 as it was in v3, so a bare border takes the text colour unless you give it one.
How Tailwind finds your classes
Tailwind only generates the classes you use, so it has to read your files. In v3 you listed the paths in a content array, and forgetting a folder meant missing styles. In v4 Tailwind finds template files on its own. The announcement says it skips anything in .gitignore, and binary files such as images and archives.
If some of your templates live somewhere Tailwind skips, such as a component library inside node_modules, point at it from CSS with @source:
@import "tailwindcss";
@source "../../node_modules/@my-company/ui";
The path is relative to the CSS file. For a normal Vue project with components under src/, you will not need @source at all.
Upgrade a v3 project
For existing projects there is an upgrade tool:
npx @tailwindcss/upgrade
It needs Node 20 or newer, and it stops if git shows uncommitted changes, so commit first. The upgrade guide recommends running it on a new branch and reviewing the diff.
The tool updates your dependencies, moves your config into @theme, swaps the directives for the import, and renames the utilities whose scale shifted. Those renames are what to check in the diff:
shadow-smis nowshadow-xs, and the oldshadowisshadow-sm.bluranddrop-shadowshift the same way.rounded-smis nowrounded-xs, and the oldroundedisrounded-sm.ringis nowring-3, because the default ring width went from 3px to 1px. The default ring colour also changed fromblue-500tocurrentColor, so addring-blue-500where you relied on it.outline-noneis nowoutline-hidden.
The tool deletes tailwind.config.js after moving its settings into CSS. If it cannot convert the file, it says so and keeps it, loaded from your CSS with an @config line. Keep it until you have moved those settings by hand.
The tool moves your PostCSS setup to the new @tailwindcss/postcss package, not to the Vite plugin. To switch, install and add @tailwindcss/vite as shown above, then remove Tailwind from postcss.config.js, or delete the file if nothing else is in it.