Prerelease Documentation
Preview what's coming in the next version of Tailwind CSS.
After a long alpha period, we’re pumped to finally promote Tailwind CSS v4.0 to beta!
There are definitely some rough edges and things we want to improve, but we’re confident that we’re not going to make any more breaking changes between now and the stable release.
This documentation is a work-in-progress and we’ll continue to improve it over the course of the beta period, but it should be enough to get you up and running.
If you run into any snags, let us know on GitHub so we can bullet-proof this thing for the stable release a couple months down the road.
If you’re using Vite or a Vite-powered framework like SvelteKit or Remix, install Tailwind along with our new dedicated Vite plugin:
$ npm install tailwindcss@next @tailwindcss/vite@next
Next, add our Vite plugin to your vite.config.ts
file:
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
tailwindcss()
],
});
Finally, import Tailwind into your main CSS file:
@import "tailwindcss";
If your project uses PostCSS or you’re using a framework like Next.js that supports PostCSS plugins, install Tailwind along with our new dedicated PostCSS plugin:
$ npm install tailwindcss@next @tailwindcss/postcss@next
Next, add our PostCSS plugin to your postcss.config.js
file:
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};
Finally, import Tailwind into your main CSS file:
@import "tailwindcss";
If you want to use our dedicated CLI tool, install Tailwind along with our new dedicated CLI package:
$ npm install tailwindcss@next @tailwindcss/cli@next
Next, import Tailwind into your main CSS file:
@import "tailwindcss";
Then compile your CSS using the CLI tool:
$ npx @tailwindcss/cli -i input.css -o output.css
You can also download standalone builds of the new CLI tool from GitHub for projects that don’t otherwise depend on the Node.js ecosystem.
If you’d like to try upgrading a project from v3 to the v4 beta releases, you can use our upgrade tool to do the vast majority of the heavy lifting for you:
$ npx @tailwindcss/upgrade@next
For most projects, the upgrade tool will automate the entire migration process including updating your dependencies, migrating your configuration file to CSS, and handling any changes to your template files.
We recommend running the upgrade tool in a new branch, then carefully reviewing the diff and testing your project in the browser to make sure all of the changes look correct. You may need to tweak a few things by hand in complex projects, but the tool will save you a ton of time either way.
It’s also a good idea to go over all of the breaking changes in v4.0 and get a good understanding of what’s changed, in case there are other things you need to update in your project that the upgrade tool doesn’t catch.
Tailwind CSS v4.0 is a ground-up rewrite of the framework, taking everything we’ve learned about the architecture over the years and optimizing it to be as fast as possible.
When benchmarking it on our own projects, we’ve found full rebuilds to be over 3.5x faster, and incremental builds to be over 8x faster.
Here are the median build times we saw when we benchmarked Tailwind CSS v4.0 against Catalyst:
v3.4 | v4.0 Beta | Improvement | |
---|---|---|---|
Full build | 378ms | 100ms | 3.78x |
Incremental rebuild with new CSS | 44ms | 5ms | 8.8x |
Incremental rebuild with no new CSS | 35ms | 192µs | 182x |
The most impressive improvement is on incremental builds that don’t actually need to compile any new CSS — these builds are over 100x faster and complete in microseconds. And the longer you work on a project, the more of these builds you run into because you’re just using classes you’ve already used before, like flex
, col-span-2
, or font-bold
.
One of the biggest changes in Tailwind CSS v4.0 is the shift from configuring your project in JavaScript to configuring it in CSS.
Instead of a tailwind.config.js
file, you can configure all of your customizations directly in the CSS file where you import Tailwind, giving you one less file to worry about in your project:
@import "tailwindcss";
@theme {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 1920px;
--color-avocado-100: oklch(0.99 0 0);
--color-avocado-200: oklch(0.98 0.04 113.22);
--color-avocado-300: oklch(0.94 0.11 115.03);
--color-avocado-400: oklch(0.92 0.19 114.08);
--color-avocado-500: oklch(0.84 0.18 117.33);
--color-avocado-600: oklch(0.53 0.12 118.34);
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
/* ... */
}
The new CSS-first configuration lets you do just about everything you could do in your tailwind.config.js
file, including configuring your design tokens, setting up content sources, defining custom utilities and variants, installing plugins, and more.
To learn more about how it all works, read the CSS configuration in-depth documentation.
Tailwind CSS v4.0 takes all of your design tokens and makes them available as CSS variables by default, so you can reference any value you need at run-time using just CSS.
Using the example @theme
from earlier, all of these values will be added to your CSS to as regular custom properties:
:root {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 1920px;
--color-avocado-100: oklch(0.99 0 0);
--color-avocado-200: oklch(0.98 0.04 113.22);
--color-avocado-300: oklch(0.94 0.11 115.03);
--color-avocado-400: oklch(0.92 0.19 114.08);
--color-avocado-500: oklch(0.84 0.18 117.33);
--color-avocado-600: oklch(0.53 0.12 118.34);
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
/* ... */
}
This makes it easy to reuse these values as inline styles or pass them to libraries like Motion to animate them.
We’re using real CSS cascade layers in v4.0, which make it easier than ever to control the precedence of styles and how they interact with each other.
Here’s what the output looks like when you build your CSS with v4.0:
We’ve had layers as a concept in Tailwind for years, but native cascade layers can do things that we couldn’t easily replicate at build-time, like isolating styles within a layer even if they have a higher specificity than styles in another layer. Less code for us to maintain too!
You know how you always had to configure that annoying content
array in Tailwind CSS v3? In v4.0, we came up with a bunch of heuristics for detecting all of that stuff automatically so you don’t have to configure it at all.
For example, we automatically ignore anything in your .gitignore
file to avoid scanning dependencies or generated files that aren’t under version control:
# dependencies
/node_modules
# testing
/coverage
# caches
/.next/
# production
/build
We also automatically ignore all binary extensions like images, videos, .zip files, and more.
And if you ever need to explicitly add a source that’s excluded by default, you can always add it with the @source
directive, right in your CSS file:
@import "tailwindcss";
@source "../node_modules/@my-company/ui-lib";
The @source
directive uses the same heuristics under the hood, so it will exclude binary file types for example as well, without you having to specify all of the extensions to scan explicitly.
Before v4.0, if you wanted to inline other CSS files using @import
you’d have to configure another plugin like postcss-import
to handle it for you.
Now we handle this out of the box, so you don’t need any other tools:
export default {
plugins: {
'postcss-import': {},
'@tailwindcss/postcss': {},
},
};
Our import system is purpose-built for Tailwind CSS, so we’ve also been able to make it even faster by tightly integrating it with our engine.
When building for production, Tailwind CSS v4.0 runs your CSS through Lightning CSS automatically, which handles things like vendor prefixes, modern feature transpilation, minification, and more.
This means you can remove tools like autoprefixer
and postcss-preset-env
from your project as well:
export default {
plugins: {
'@tailwindcss/postcss': {},
'postcss-preset-env': {},
'autoprefixer': {},
},
};
In v4.0, Tailwind CSS is the only thing you need to set up to handle your entire CSS pipeline — no other tooling required.
In v4.0, we’ve really slimmed down the amount of theme configuration you need to do, especially for things that aren’t really design tokens.
Utilities like grid-cols-12
, z-40
, and opacity-70
are no longer based on your theme — they just work. Whether you need a 5 column grid or a 73 column grid, you don’t need to configure anything to make it happen.
<div class="grid grid-cols-73">
<div>1</div>
<!-- ... -->
<div>73</div>
</div>
We’ve applied the same simplifications to variants like data-*
as well — you don’t need to configure these at all anymore or use arbitrary values for simple boolean attributes:
<div class="opacity-50 data-[selected]:opacity-100" data-selected>
<div class="opacity-50 data-selected:opacity-100" data-selected>
<!-- ... -->
</div>
These changes mean you touch your theme configuration way less frequently, and it stays focused on the design tokens that matter, like your typography, color palette, and breakpoints.
We’ve simplified the way spacing utilities like px-*
, mt-*
, w-*
, h-*
, and more work by deriving them all from a single spacing scale value, defined as 0.25rem
in the default theme:
@theme {
--spacing: 0.25rem;
}
When you define your spacing scale this way, every multiple of 0.25rem
is available in your spacing scale. This means utilities like mt-21
will work with no extra configuration, unlike in v3 where you had to choose between mt-20
and mt-24
or drop down to using an arbitrary value.
And if you want more constraints, you can always disable the --spacing
variable and provide your own explicit scale:
@theme {
--spacing: initial
--spacing-1: 0.25rem
--spacing-2: 0.5rem
--spacing-4: 1rem
--spacing-8: 2rem
--spacing-12: 3rem
}
We’ve upgraded the entire default color palette from rgb
to oklch
, taking advantage of the wider gamut to make the colors more vivid in places where we were previously limited by the sRGB color space.
We’ve tried to keep the balance between all the colors the same as it was in v3, so even though we’ve refreshed things across the board, it shouldn’t feel like a breaking change when upgrading your existing projects.
If you were using CSS variables in your color palette in v3, you might remember having to do weird things like define your colors as just a list of numbers without including the rgb(…)
function, or having to use the <alpha-value>
placeholder so that opacity modifiers would work.
Thanks to the new CSS color-mix(…)
function, none of that is necessary in v4.0 — you just define your colors as variables and all of the opacity modifier features work automatically:
@import "tailwindcss";
@theme {
--color-primary: var(--color-blue-500);
--color-error: var(--color-red-500);
/* ... */
}
Now when you go to use a utility like bg-primary/50
, it just works — no cryptic workarounds necessary:
<div class="bg-primary/50">
<!-- ... -->
</div>
We’ve brought container query support into core for v4.0, so you don’t need the @tailwindcss/container-queries
plugin anymore:
<div class="@container">
<div class="grid grid-cols-1 @sm:grid-cols-3 @lg:grid-cols-4">
<!-- ... -->
</div>
</div>
We’ve also added support for max-width container queries using the new @max-*
variant:
<div class="@container">
<div class="grid grid-cols-3 @max-md:grid-cols-1">
<!-- ... -->
</div>
</div>
Like our regular breakpoint variants, you can also stack @min-*
and @max-*
variants to define container query ranges:
<div class="@container">
<div class="flex @min-md:@max-xl:hidden">
<!-- ... -->
</div>
</div>
Browser support for container queries is really great now, and I’m excited to make it even easier to start using them in your projects in v4.0.
We’ve finally added APIs for doing 3D transforms, like rotate-x-*
, rotate-y-*
, scale-z-*
, translate-z-*
, and tons more.
<div class="perspective-distant">
<article class="... transform-3d rotate-x-51 rotate-z-43 shadow-xl transition-all duration-500 hover:-translate-y-4 hover:rotate-x-49 hover:rotate-z-38 hover:shadow-2xl">
<!-- ... -->
</article>
</div>
Use the transform-3d
utility to enable 3D transforms by setting the right transform-style
Use the rotate-x-*
, rotate-y-*
, and rotate-z-*
utilities to rotate elements in 3D space.
All of these utilities support any numeric value automatically out of the box, but here are a few examples for reference:
Use the new scale-z-*
utilities to scale elements on the z-axis.
You can use any numeric value you want automatically out of the box, but here are a few examples for reference:
Use the new translate-z-*
utilities to move elements closer or further away:
This utility uses your spacing scale by default and supports all of those values out of the box, but here are a few examples for reference:
Use utilities like perspective-near
, perspective-normal
, and perspective-distant
along with the new perspective-origin-*
utilities to control the perspective used for 3D transforms:
The perspective-*
utilities can all be customized using the --perspective-*
namespace in your theme.
Use the new backface-visible
and backface-hidden
utilities to control whether the back of an element is visible when transformed in 3D space.
Linear gradients now support angles as values, so you can use utilities like bg-linear-45
to create a gradient on a 45 degree angle:
<div class="bg-linear-45 from-indigo-500 via-purple-500 to-pink-500"></div>
You may notice we’ve renamed bg-gradient-*
to bg-linear-*
too — you’ll see why shortly!
We’ve added the ability to control the color interpolation mode for gradients using a modifier, so a class like bg-linear-to-r/srgb
interpolates using sRGB, and bg-linear-to-r/oklch
interpolates using OKLCH:
<div class="bg-linear-to-r/srgb from-indigo-500 to-teal-400"></div>
<div class="bg-linear-to-r/oklch from-indigo-500 to-teal-400"></div>
Using polar color spaces like OKLCH or HSL can lead to much more vivid gradients when the from-*
and to-*
colors are far apart on the color wheel. We’re using OKLCH by default in v4.0 but you can always interpolate using a different color space by adding one of these modifiers.
We’ve added new bg-conic-*
and bg-radial-*
utilities for creating conic and radial gradients:
<div class="bg-conic/[in_hsl_longer_hue] from-red-600 to-red-600 size-24 rounded-full"></div>
<div class="bg-radial-[at_25%_25%] from-white to-zinc-900 to-75% size-24 rounded-full"></div>
These new utilities work alongside the existing from-*
, via-*
, and to-*
utilities to let you create conic and radial gradients the same way you create linear gradients, and include modifiers for setting the color interpolation method and arbitrary value support for controlling details like the gradient position.
We’ve added dedicated inset-shadow-*
and inset-ring-*
utilities in v4.0 that can be composed with the existing shadow-*
and ring-*
utilities, giving you four layers of shadows you can stack to create the effects you need for your projects.
Send
Send
<button class="shadow-md inset-shadow-sm inset-shadow-white/20 ring ring-blue-600 inset-ring inset-ring-white/15 ...">
<!-- ... -->
</button>
The inset-ring-*
utilities support any width value just like the ring-*
utilities, and the inset-shadow-*
utilities ship with 2xs
, xs
, and sm
sizes out of the box. We may add more down the road but those ones feel the most useful right now.
@theme {
--inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05);
--inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05);
--inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05);
}
Just like the regular shadow-*
and ring-*
utilities, these both support colors as well using classes like inset-shadow-black/25
and inset-ring-white/50
.
We’ve added utilities for the new field-sizing
property that lets you create auto-resizing textareas with just CSS:
Type in the textarea to see the effect
<label class="block">
<span class="block text-sm/6 font-medium text-gray-900 dark:text-white">Add your comment</span>
<textarea class="field-sizing-content ..."></textarea>
</label>
Use field-sizing-content
to make the control resize to fit its contents, or field-sizing-fixed
to give the control a fixed size.
Ever been annoyed that your app was showing light scrollbars in dark mode? You want these new color-scheme
utilities.
Scroll the content to see the scrollbar themes
Light mode
Right now there are six-hundred Titleists that I got from the driving range in the trunk of my car. Why don't we drive out to Rock-a-Way… and hit `em into the ocean! Now picture this. we find a nice sweet spot between the dunes, we take out our drivers, we tee up and, that ball goes sailing up into the sky holds there for a moment and then.. gulp!
Dark mode
Right now there are six-hundred Titleists that I got from the driving range in the trunk of my car. Why don't we drive out to Rock-a-Way… and hit `em into the ocean! Now picture this. we find a nice sweet spot between the dunes, we take out our drivers, we tee up and, that ball goes sailing up into the sky holds there for a moment and then.. gulp!
<div class="grid grid-cols-2">
<div class="bg-white overflow-y-scroll scheme-light">
...
</div>
<div class="bg-slate-800 overflow-y-scroll scheme-dark">
...
</div>
</div>
Here’s a full list of all the new APIs:
Throw scheme-light dark:scheme-dark
on your html
or body
element and your scrollbars will always look good, no matter which dark mode strategy you use.
We’ve added utilities for the new-ish font-stretch
property, which helps you style variable fonts that support different widths:
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
Looks like the name of this is changing to font-width
at some point but no browsers actually support it yet, looking forward to dealing with that.
In the new v4.0 engine, certain variants can be chained with other variants, letting you use simple named APIs for things that needed complex arbitrary variants in v3:
<div class="group">
<div class="group-has-[&[data-potato]]:opacity-100">
<div class="group-has-data-potato:opacity-100">
<!-- ... -->
</div>
<div data-potato>
<!-- ... -->
</div>
</div>
This works with any variant where it makes sense, including group-*
, peer-*
, has-*
, and the new not-*
and in-*
variants. You can chain as many of them as you want, so even totally useless classes like group-not-has-peer-not-data-active:underline
will generate real CSS.
The new starting
variant adds support for the new CSS @starting-style
feature, making it possible to transition element properties when an element is first displayed:
Click the button to see the popover animate in
<div>
<button popovertarget="my-popover">Check for updates</button>
<div popover id="my-popover" class="opacity-0 transition-all duration-500 transition-discrete open:opacity-100 starting:open:opacity-0">
<!-- ... -->
</div>
</div>
The new not-*
variant adds support for the :not(…)
pseudo-class, letting you style things when certain conditions are not true.
For example, only adding hover styles to a button when the button is not focused:
<button class="bg-indigo-600 hover:not-focus:bg-indigo-700">
<!-- ... -->
</button>
You can also combine the not-*
variant with media query variants like forced-colors
to only style an element when forced colors mode is not active:
<input type="radio" class="not-forced-colors:appearance-none" />
It works with supports-*
variants too, so you can style an element based on the lack of browser support for a specific CSS feature:
<div class="not-supports-[display:grid]:flex">
<!-- ... -->
</div>
The new inert
variant lets you style elements marked with the inert
attribute:
<main inert class="inert:opacity-50 inert:blur">
<!-- ... -->
</main>
This is useful for adding visual cues that make it clear that an element isn’t interactive.
We’ve added four new variants for the :nth-child(…)
, :nth-last-child(…)
, :nth-of-type(…)
, and :nth-last-of-type(…)
pseudo-classes:
<div class="nth-3:underline">…</div>
<div class="nth-last-5:underline">…</div>
<div class="nth-of-type-4:underline">…</div>
<div class="nth-last-of-type-6:underline">…</div>
You can pass any number you want to these by default, and use arbitrary values for more complex expressions like nth-[2n+1_of_li]
.
You know our group-*
variants like group-focus
? The new in-*
variant is just like that except you don’t need to add group
to the parent element:
<div tabindex="0" class="group">
<div class="opacity-50 group-focus:opacity-100">
<div tabindex="0">
<div class="opacity-50 in-focus:opacity-100">
<!-- ... -->
</div>
</div>
You’ll still want the group-*
stuff a lot of the time when you need fine control, but this will save you some characters the rest of the time.
We’ve updated our existing open
variant to target the :popover-open
pseudo-class as well as the [open]
attribute:
<div>
<button popovertarget="my-popover">Open Popover</button>
<div popover id="my-popover" class="opacity-0 open:opacity-100 ...">
<!-- ... -->
</div>
</div>
I’m sure I’m eventually going to regret not making it a separate popover-open
variant but I thought really hard about it and couldn’t think of any situations where an element would use both [open]
and :popover-open
and have different styles for each condition. Someone is going to update the spec and screw me on this one down the road though for sure.
You know the *
variant we shipped a while ago for targeting direct children?
<ul class="*:p-4">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
In v4.0 we’ve added a new **
variant for targeting all descendants — most useful in my opinion if you combine it with another variant for narrowing the thing you’re selecting:
<div class="**:data-avatar:rounded-full">
<div>
<img src="…" data-avatar /> <!-- This element will be round -->
</div>
<p>…</p>
</div>
Fun fact — the syntax is inspired by globs, for better or for worse.
To customize your theme in Tailwind CSS v4.0, use the new @theme
directive directly in your CSS:
@import "tailwindcss";
@theme {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 1920px;
--color-avocado-100: oklch(0.99 0 0);
--color-avocado-200: oklch(0.98 0.04 113.22);
--color-avocado-300: oklch(0.94 0.11 115.03);
--color-avocado-400: oklch(0.92 0.19 114.08);
--color-avocado-500: oklch(0.84 0.18 117.33);
--color-avocado-600: oklch(0.53 0.12 118.34);
--ease-fluid: cubic-bezier(0.3, 0, 0, 1);
--ease-snappy: cubic-bezier(0.2, 0, 0, 1);
/* ... */
}
Each CSS variable you define here tells Tailwind to make new utilities and variants available based on those values, letting you use classes like font-display
, 3xl:max-w-xl
, text-avocado-400
, and hover:ease-fluid
in your markup:
<div class="max-w-lg 3xl:max-w-xl">
<h1 class="font-display text-4xl">
Data to <span class="text-avocado-400">enrich</span> your online business
</h1>
</div>
Each set of variables is part of a namespace that links them to the corresponding utilities, for example the font size utilities reference the --font-*
namespace, all of the color utilities reference the --color-*
namespace, and the transition-timing-function utilities reference the --ease-*
namespace.
For a full list, see the theme namespace reference.
By default, adding new CSS variables behaves like extend
in Tailwind CSS v3:
@import "tailwindcss";
@theme {
/* These values are added in addition to the defaults */
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 1920px;
}
To override an entire namespace, unset the namespace using syntax like --font-*: initial
:
@import "tailwindcss";
@theme {
--font-*: initial;
--font-display: "Satoshi", "sans-serif";
}
Now the default font-sans
, font-serif
, and font-mono
utilities won’t exist in your project and font-display
will be the only available font family utility.
You can also unset the entire default theme using --*: initial
if you want to start completely from scratch:
@import "tailwindcss";
@theme {
--*: initial;
}
This will remove all of the default design tokens, including all of the default fonts, the typography scale, the color palette, and more.
To set the default line height, font weight, or letter spacing for a custom font size, add a supporting variable using double-dashes like --text-big--line-height
:
@theme {
--text-big: 16rem;
--text-big--line-height: 18rem;
--text-big--font-weight: 550;
--text-big--letter-spacing: -0.025em;
}
By default, Tailwind CSS v4.0 preserves any custom @keyframes
rules you add to your CSS, even if you don’t use the corresponding animation utilities in your project.
To make sure unused @keyframes
rules are removed, configure them under @theme
instead of at the root of your CSS:
@theme {
--animate-marquee: marquee 3s linear infinite;
@keyframes marquee {
to {
transform: translateY(-50%);
}
}
}
Since we’ve dramatically simplified theme configuration in Tailwind CSS v4.0, you’ll only generally work with these namespaces:
Namespace | Utilities |
---|---|
--color-* | Color utilities like bg-white , text-black , or fill-blue-500 |
--font-* | Font family utilities like font-sans |
--text-* | Font size utilities like text-sm |
--font-weight-* | Font weight utilities like font-bold |
--tracking-* | Letter spacing utilities like tracking-tight |
--leading-* | Line height utilities like leading-relaxed |
--breakpoint-* | Breakpoint variants like md:* and lg:* |
--container-* | Container query variants like @sm:* and @lg:* and max-width utilities like max-w-lg |
--radius-* | Border radius utilities like rounded-md |
--shadow-* | Box shadow utilities like shadow-lg |
--inset-shadow-* | Inset box shadow utilities like inset-shadow-sm |
--drop-shadow-* | Drop shadow utilities like drop-shadow-xl |
--ease-* | Transition timing function utilities like ease-out |
--animate-* | Animation utilities like animate-spin |
If you need more fine-grained control, most utilities can also be configured under a namespace that matches the CSS property name. For example custom background-image
utilities like bg-grid-pattern
can be configured using --background-image-grid-pattern: url(…)
.
By default, the dark
variant in Tailwind CSS v4.0 uses the prefers-color-scheme
media query.
If you want to use a selector-based strategy in your project for dark mode, override the dark
variant with the selector you want to use:
@import "tailwindcss";
@variant dark (&:where(.dark, .dark *));
If the automatic source detection in Tailwind CSS v4.0 is too broad and including files you don’t want it to include (maybe you’re working in a large monorepo for example), you can use the source(…)
function when importing Tailwind to specify the base path for automatic source detection:
@import "tailwindcss" source("../src");
This path should be relative to the CSS file where it’s used.
If you need to add additional content sources that aren’t being picked up by default (like something that is in your .gitignore
file), add it using @source
:
@import "tailwindcss";
@source "../node_modules/@my-company/ui-lib/src/components";
For situations like this, it can also be helpful to export a CSS file from your library and move the @source
directive there instead so you can just import the CSS file:
@source "./src/components";
@import "tailwindcss";
@import "@my-company/ui-lib";
The @source
directive can also be useful when you’re using the Vite plugin but need to include content sources that aren’t naturally part of the module graph, like PHP templates in a Laravel project:
@import "tailwindcss";
@source "../../resources/views";
@source "../../app";
If you need disable automatic source detection for any reason, use source(none)
when importing Tailwind:
@import "tailwindcss" source(none);
With source detection disabled, you can then just use @source
to configure all of your content sources explicitly.
If you need to disable Tailwind’s base styles, you can import the pieces of Tailwind that you need separately:
@layer theme, base, components, utilities;
@import "tailwindcss/theme" layer(theme);
@import "tailwindcss/utilities" layer(utilities);
To prefix your utilities and theme variables to avoid conflicts with existing CSS, use the prefix(…)
function when importing Tailwind:
@import "tailwindcss" prefix(tw);
Prefixes work a little differently than in v3 — now they look like variants and are always at the beginning of the class name:
<div class="tw:flex tw:bg-red-500 tw:hover:bg-red-600">
<!-- ... -->
</div>
When using a prefix, you should still configure your theme variables as if you aren’t using a prefix:
@import "tailwindcss" prefix(tw);
@theme {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 1920px;
--color-avocado-100: oklch(0.99 0 0);
--color-avocado-200: oklch(0.98 0.04 113.22);
--color-avocado-300: oklch(0.94 0.11 115.03);
/* ... */
}
The generated CSS variables will include a prefix though to avoid conflicts with any existing variables in your project:
:root {
--tw-font-display: "Satoshi", "sans-serif";
--tw-breakpoint-3xl: 1920px;
--tw-color-avocado-100: oklch(0.99 0 0);
--tw-color-avocado-200: oklch(0.98 0.04 113.22);
--tw-color-avocado-300: oklch(0.94 0.11 115.03);
/* ... */
}
To add a custom utility in v4.0, use the new @utility
directive:
@import "tailwindcss";
@utility tab-4 {
tab-size: 4;
}
Custom utilities are automatically inserted into the utilities
layer along with all of the built-in utilities in the framework.
To add a custom variant in v4.0, use the new @variant
directive:
@import "tailwindcss";
@variant pointer-coarse (@media (pointer: coarse));
@variant theme-midnight (&:where([data-theme="midnight"] *));
This lets you write utilities like pointer-coarse:size-48
and theme-midnight:bg-slate-900
.
To load a plugin in v4.0, use the the new @plugin
directive:
@import "tailwindcss";
@plugin "@tailwindcss/typography";
The @plugin
directive takes either a package name or a local path.
To use an existing JS configuration file in v4.0, load it with the @config
directive:
@import "tailwindcss";
@config "../../tailwind.config.js";
Note that not every feature of the JS config is supported in v4.0. Options like corePlugins
, important
, and separator
will likely not be supported at all in the stable v4.0 release, and options like safelist
may return but with differences in behavior.
If you want to use @apply
in the <style>
block of a Vue or Svelte component, you will need to import your theme configuration to make those values available in that context.
To do this without duplicating the CSS variables in your CSS output, use theme(reference)
when importing your theme:
<template>
<h1>Hello world!</h1>
</template>
<style>
@import "../../my-theme.css" theme(reference);
h1 {
@apply font-bold text-2xl text-red-500;
}
</style>
If you’re just using the default theme, you can import "tailwindcss/theme"
directly:
<template>
<h1>Hello world!</h1>
</template>
<style>
@import "tailwindcss/theme" theme(reference);
h1 {
@apply font-bold text-2xl text-red-500;
}
</style>
Tailwind CSS v4.0 is a new major version of the framework, and while we strive to preserve backward compatibility as much as possible, there are several breaking changes we’ve had to make to make the improvements we wanted for the new release.
To make the upgrade as painless as possible, we’ve built a really awesome migration tool that will automate basically all of these changes for you.
To upgrade your project automatically, run the upgrade tool from your project root on the command-line:
$ npx @tailwindcss/upgrade@next
Once it’s done, review all of the changes and test your project to make sure everything is working as expected, and with any luck you’ll be off to the races.
But here’s a list of all of the changes in detail in case you run into issues using the migration tool.
In Tailwind CSS v3, the tailwindcss
package was a PostCSS plugin, but in v4.0 the PostCSS plugin lives in a dedicated @tailwindcss/postcss
package.
Tailwind CSS v4.0 also handles CSS imports and vendor prefixing for you, so you can remove postcss-import
and autoprefixer
if they are in your project:
export default {
plugins: {
'postcss-import': {},
'tailwindcss': {},
'autoprefixer': {},
'@tailwindcss/postcss': {},
},
};
If you’re using Vite, we recommend migrating from the PostCSS plugin to our new dedicated Vite plugin:
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [
tailwindcss()
],
});
In v4.0, Tailwind CLI lives in a dedicated @tailwindcss/cli
package. Update any of your build commands to use the new package instead:
npx tailwindcss -i input.css -o output.css
npx @tailwindcss/cli -i input.css -o output.css
In Tailwind CSS v4.0, you import Tailwind using a regular CSS @import
statement, not using the @tailwind
directives you used in v3:
@tailwind base;
@tailwind components;
@tailwind utilities;
@import "tailwindcss";
We’ve removed any utilities that were deprecated in v3 and have been undocumented for several years. Here’s a list of what’s been removed along with the modern alternative:
Deprecated | Replacement |
---|---|
bg-opacity-* | Use opacity modifiers like bg-black/50 |
text-opacity-* | Use opacity modifiers like text-black/50 |
border-opacity-* | Use opacity modifiers like border-black/50 |
divide-opacity-* | Use opacity modifiers like divide-black/50 |
ring-opacity-* | Use opacity modifiers like ring-black/50 |
placeholder-opacity-* | Use opacity modifiers like placeholder-black/50 |
flex-shrink-* | shrink-* |
flex-grow-* | grow-* |
overflow-ellipsis | text-ellipsis |
decoration-slice | box-decoration-slice |
decoration-clone | box-decoration-clone |
We’ve updated the default styles in Preflight for form elements to look more like form elements. They aren’t designed by any means, and are meant to look like user-agent styles but be a bit easier to customize.
We’re making this change because we hated that inputs in v3 were totally invisible by default, which felt opinionated and surprising, and made them inaccessible out of the box until you customized them.
The easiest way to update an existing project to account for these changes is to reset these styles in your own CSS:
@import "tailwindcss";
@layer base {
input,
textarea,
select,
button {
border: 0px solid;
border-radius: 0;
padding: 0;
color: inherit;
background-color: transparent;
color: inherit;
}
}
This is a pretty significant change to our base styles, but we’re optimistic that once everyone gets used to it that it’ll feel like a much better default.
In v3, the container
utility had several configuration options like center
and padding
that no longer exist in v4.0. To customize the container
utility in v4.0, extend it with @utility
:
@import "tailwindcss";
@utility container {
margin-inline: auto;
padding-inline: 2rem;
}
We’ve shifted things around a bit in the default shadow scales to make sure every shadow utility has a named value.
To do this, we’ve renamed shadow
to shadow-sm
, shadow-sm
to shadow-xs
, drop-shadow
to drop-shadow-sm
, and drop-shadow-sm
to drop-shadow-xs
:
v3 | v4 |
---|---|
shadow-sm | shadow-xs |
shadow | shadow-sm |
drop-shadow-sm | drop-shadow-xs |
drop-shadow | drop-shadow-sm |
The shadow
and drop-shadow
utilities will still work for backward compatibility, but shadow-sm
and drop-shadow-sm
will look different in your project if you don’t replace each instance with shadow-xs
and drop-shadow-xs
instead.
We’ve shifted things around a bit in the default blur scale to make sure every blur utility has a named value.
To do this, we’ve renamed blur
to blur-sm
, and blur-sm
to blur-xs
:
v3 | v4 |
---|---|
blur-sm | blur-xs |
blur | blur-sm |
The blur
utility will still work for backward compatibility, but blur-sm
will look different in your project if you don’t replace each instance with blur-xs
instead.
We’ve shifted things around a bit in the default border radius scale to make sure every border radius utility has a named value.
To do this, we’ve renamed rounded
to rounded-sm
, and rounded-sm
to rounded-xs
:
v3 | v4 |
---|---|
rounded-sm | rounded-xs |
rounded | rounded-sm |
The rounded
utility will still work for backward compatibility, but rounded-sm
will look different in your project if you don’t replace each instance with rounded-xs
instead.
In v3, borders used your configured gray-200
color by default. We’ve updated this in v4 to be just currentColor
, which matches the default behavior of all browsers.
To update your project for this change, make sure anywhere you’re using a border color utility anywhere you’re using the border
utility, or add these lines of CSS to your project to preserve the v3 behavior:
@import "tailwindcss";
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
In v3, the ring
utility added a 3px ring. We’ve changed this in v4 to be 1px to make it consistent with borders and outlines.
To update your project for this change, replace any usage of ring
with ring-3
:
<div class="ring ring-blue-500">
<div class="ring-3 ring-blue-500">
<!-- ... -->
</div>
In v3, placeholder text used your configured gray-400
color by default. We’ve simplified this in v4 to just use the current text color at 50% opacity.
You probably won’t even notice this change (it might even make your project look better), but if you want to preserve the v3 behavior, add this CSS to your project:
@import "tailwindcss";
@layer base {
input::placeholder,
textarea::placeholder {
color: theme(--color-gray-400);
}
}
In v3, the outline-none
utility was actually a complex class that didn’t just set outline-style: none
:
/* v3 */
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
What it really did was add an invisible 2px outline that would still show up in forced colors mode for accessibility reasons.
We’ve simplified this in v4.0 and now outline-none
just sets outline-style: none
like you’d expect:
/* v4 */
.outline-none {
outline-style: none;
}
We’ve added a new outline-hidden
utility that does what outline-none
did in v3, since it’s still a very useful feature.
To update your project for this change, replace any use of outline-none
with outline-hidden
, unless you really do want outline-style: none
:
<input class="focus:outline-none">
<input class="focus:outline-hidden">
In v3, any custom classes you defined within @layer utilities
would get picked up by Tailwind as a true utility class and would automatically work with variants like hover
, focus
, or lg
.
In v4.0 we are using native cascade layers and no longer hijacking the @layer
at-rule, so we’ve introduced the @utility
API as a replacement:
@layer utilities {
.tab-4 {
tab-size: 4;
}
}
@utility tab-4 {
tab-size: 4;
}
Custom utilities must be a single class name in v4.0 and not a complex selector. If your custom utility is more complex than a single class name, use nesting to define the utility:
@utility scrollbar-hidden {
&::-webkit-scrollbar {
display: none;
}
}
In v3, stacked variants were applied from right to left, but in v4 we’ve updated them to apply left to right to look more like CSS syntax.
To update your project for this change, reverse the order of any order-sensitive stacked variants in your project:
<ul class="py-4 first:*:pt-0 last:*:pb-0">
<ul class="py-4 *:first:pt-0 *:first:pb-0">
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
You likely have very few of these if any — the direct child variant (*
) and any typography plugin variants (prose-headings
) are the most likely ones you might be using, and even then it’s only if you’ve stacked them with other variants.
In v3 you were able to use CSS variables as arbitrary values without var(…)
, but recent updates to CSS mean that this can often be ambiguous, so we’ve changed the syntax for this in v4.0 to use parentheses instead of square brackets.
To update your project for this change, replace usage of the old variable shorthand syntax with the new variable shorthand syntax:
<div class="bg-[--brand-color]">
<div class="bg-(--brand-color)">
<!-- ... -->
</div>
In v3 we exported a resolveConfig
function that you could use to turn your JS config into a flat object that you could use in your other JavaScript.
We’ve removed this in v4 in hopes that people can use the CSS variables we generate directly instead, which is much simpler and will significantly reduce your bundle size.
In v4.0 we’ve updated the hover
variant to only apply when the primary input device supports hover:
@media (hover: hover) {
.hover\:underline:hover {
text-decoration: underline;
}
}
This can create problems if you’ve built your site in a way that depends on touch devices triggering hover on tap. If this is an issue for you, you can override the hover
variant with your own variant that uses the old implementation:
@import "tailwindcss";
@variant hover (&:hover);
Generally though I’d recommend treating hover functionality as an enhancement, and not depending on it for your site to work since touch devices don’t truly have the ability to hover.
In v3 there was a corePlugins
option you could use to completely disable certain utilities in the framework. This is no longer supported in v4.0, but we’re planning to explore different ideas for solving the same type of problems that feature was often used to solve in later beta releases.
Since Tailwind CSS v4.0 includes CSS variables for all of your theme values, we recommend using those variables instead of the theme()
function whenever possible:
@import "tailwindcss";
.my-class {
background-color: theme(colors.red.500);
background-color: var(--colors-red-500);
}
For cases where you still need to use the theme()
function (like in media queries where CSS variables aren’t supported), you should use the CSS variable name instead of the old dot notation:
@import "tailwindcss";
@media (width >= theme(screens.xl)) {
@media (width >= theme(--breakpoint-xl)) {
/* ... */
}
JavaScript config files are still supported for backward compatibility, but they are no longer detected automatically in v4.0.
If you still need to use a JavaScript config file, make sure to load it explicitly using @config
:
@import "tailwindcss";
@config "../../tailwind.config.js";