Starting from TailwindCSS v4, the CSS-first configuration is preferred, meaning the init process and the tailwind.config.js file have been removed.
- What's breaking changes from TailwindCSS v4
- New CSS-first configuration from TailwindCSS v4
- Removed
@tailwinddirectives - Deprecated: Sass, Less and Stylus preprocessors support
Customize theme with CSS-first directives
In a CSS-first configuration, several directives are available. For theme customization, you can primarily use the @theme directive:
@import "tailwindcss";
@theme {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 120rem;
--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);
/* ... */
}
@themedirective
Theme variables are defined in namespaces and each namespace corresponds to one or more utility class or variant APIs.
- Theme variable namespaces
- Default theme variable reference
Set dark directive with CSS-first
Starting from v4, a new @custom-variant directive is introduced, allowing you to customize the behavior of dark: or create your own custom variants, such as coffee:.
@custom-variantdirective@variantdirective
document.querySelector('button').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
<script src="https://unpkg.com/@tailwindcss/browser"></script>
<style type="text/tailwindcss">
/* changed the behavior of dark: (default: based on prefers-color-scheme) to work based on the presence of the .dark parent class */
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-pink: #eb6bd8;
}
@layer theme {
:root, :host {
@variant dark {
--color-pink: #8e0d7a;
}
}
}
</style>
<button class="size-20 bg-pink dark:text-white">Click Here</button>
<div class="w-50 h-12 bg-purple-200 dark:bg-purple-900 dark:text-white">
Lorem Ipsum
</div>
Additionally, without JS, it's possible to detect whether the system prefers light or dark mode by default, and integrate this into the dark: variant behavior. For this, your light/dark toggle needs to support three states: system/light/dark. You'll also need an extra .system class, but the dark: variant can work the same way with both .system and .dark classes:
- Manual dark mode toggle, but by default it should follow the system scheme
How to declare dark or more themes in TailwindCSS v4
Following the dark example, an unlimited number of variants and themes can be declared.
document.querySelector('#toggle-dark').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
document.documentElement.classList.remove('coffee');
});
document.querySelector('#toggle-coffee').addEventListener('click', () => {
document.documentElement.classList.toggle('coffee');
document.documentElement.classList.remove('dark');
});
<script src="https://unpkg.com/@tailwindcss/browser"></script>
<style type="text/tailwindcss">
@custom-variant dark (&:where(.dark, .dark *));
@custom-variant coffee (&:where(.coffee, .coffee *));
@theme {
--color-pink: #eb6bd8;
--color-tsunami: #77b4ea;
}
@layer theme {
:root, :host {
@variant dark {
--color-pink: #8e0d7a;
--color-tsunami: #0d84ec;
}
@variant coffee {
--color-pink: #a67ca8;
--color-tsunami: #b57913;
}
}
}
</style>
<div class="mb-4">
<button id="toggle-dark" class="px-4 py-2 bg-sky-600 hover:bg-sky-950 text-white cursor-pointer rounded-lg">Toggle Dark</button>
<button id="toggle-coffee" class="px-4 py-2 bg-amber-600 hover:bg-amber-950 text-white cursor-pointer rounded-lg">Toggle Coffee</button>
</div>
<button class="size-20 bg-pink dark:text-white coffee:text-amber-50">Hello World</button>
<div class="w-50 h-12 bg-tsunami dark:text-white coffee:text-orange-200">
Lorem Ipsum
</div>
References:
- How to override theme variables in TailwindCSS v4 -
@themevs@layer themevs:root - When should I use
*and when should I use:root, :hostas the parent selector? - How
@variant darkcan be combined with@themein a CSS-first configuration to override dark mode colors - Should I use
@themeor@theme inline? - How can I safely introduce the use of
light-dark()without increasing the minimum browser version requirement?
Disable dark mode, use light: instead of dark::
- TailwindCSS: How to do a light mode only modification?
- How to disable dark mode in TailwindCSS 4
Tailwind 4 confusion about colours
reactjs - How to use custom color themes in TailwindCSS v4 - Stack Overflow
How to semantically name your colors in tailwind v4?
Upgraded to Tailwind V4 and all my custom colors stopped working...
Can I still use tailwind.config.js?
Can I use arbitrary color values like bg-[#1e40af] in v4?
Is there a limit to how many custom colors I can add?
Videos
Hi so I'm trying out tailwind in a new react project I'm working on. After a lot of wrangling with ChatGPT I realised that it doesn't seem to know much about tailwind 4 other than it exists and I had to revert to stack overflow to figure out how to get it to work by using @themes in my config.
The thing I'm confused about though is in the tailwind 3 examples I was being given you could set things like bg-primary and bg-primaryDark in one place which makes sense and is useful as I can use it all over and update it quickly to try out different colour schemes.
Whereas in Tailwind 4 the examples seem to suggest I should be using things like bg-cyan-500 everywhere which obviously means I have to change them all if I want to update it. Seems like an anti pattern so I just wanted to check whether I've misunderstood how I should be approaching this?
Starting from TailwindCSS v4, the CSS-first configuration is preferred, meaning the init process and the tailwind.config.js file have been removed.
- What's breaking changes from TailwindCSS v4
- New CSS-first configuration from TailwindCSS v4
- Removed
@tailwinddirectives - Deprecated: Sass, Less and Stylus preprocessors support
Customize theme with CSS-first directives
In a CSS-first configuration, several directives are available. For theme customization, you can primarily use the @theme directive:
@import "tailwindcss";
@theme {
--font-display: "Satoshi", "sans-serif";
--breakpoint-3xl: 120rem;
--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);
/* ... */
}
@themedirective
Theme variables are defined in namespaces and each namespace corresponds to one or more utility class or variant APIs.
- Theme variable namespaces
- Default theme variable reference
Set dark directive with CSS-first
Starting from v4, a new @custom-variant directive is introduced, allowing you to customize the behavior of dark: or create your own custom variants, such as coffee:.
@custom-variantdirective@variantdirective
document.querySelector('button').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
<script src="https://unpkg.com/@tailwindcss/browser"></script>
<style type="text/tailwindcss">
/* changed the behavior of dark: (default: based on prefers-color-scheme) to work based on the presence of the .dark parent class */
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-pink: #eb6bd8;
}
@layer theme {
:root, :host {
@variant dark {
--color-pink: #8e0d7a;
}
}
}
</style>
<button class="size-20 bg-pink dark:text-white">Click Here</button>
<div class="w-50 h-12 bg-purple-200 dark:bg-purple-900 dark:text-white">
Lorem Ipsum
</div>
Additionally, without JS, it's possible to detect whether the system prefers light or dark mode by default, and integrate this into the dark: variant behavior. For this, your light/dark toggle needs to support three states: system/light/dark. You'll also need an extra .system class, but the dark: variant can work the same way with both .system and .dark classes:
- Manual dark mode toggle, but by default it should follow the system scheme
How to declare dark or more themes in TailwindCSS v4
Following the dark example, an unlimited number of variants and themes can be declared.
document.querySelector('#toggle-dark').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
document.documentElement.classList.remove('coffee');
});
document.querySelector('#toggle-coffee').addEventListener('click', () => {
document.documentElement.classList.toggle('coffee');
document.documentElement.classList.remove('dark');
});
<script src="https://unpkg.com/@tailwindcss/browser"></script>
<style type="text/tailwindcss">
@custom-variant dark (&:where(.dark, .dark *));
@custom-variant coffee (&:where(.coffee, .coffee *));
@theme {
--color-pink: #eb6bd8;
--color-tsunami: #77b4ea;
}
@layer theme {
:root, :host {
@variant dark {
--color-pink: #8e0d7a;
--color-tsunami: #0d84ec;
}
@variant coffee {
--color-pink: #a67ca8;
--color-tsunami: #b57913;
}
}
}
</style>
<div class="mb-4">
<button id="toggle-dark" class="px-4 py-2 bg-sky-600 hover:bg-sky-950 text-white cursor-pointer rounded-lg">Toggle Dark</button>
<button id="toggle-coffee" class="px-4 py-2 bg-amber-600 hover:bg-amber-950 text-white cursor-pointer rounded-lg">Toggle Coffee</button>
</div>
<button class="size-20 bg-pink dark:text-white coffee:text-amber-50">Hello World</button>
<div class="w-50 h-12 bg-tsunami dark:text-white coffee:text-orange-200">
Lorem Ipsum
</div>
References:
- How to override theme variables in TailwindCSS v4 -
@themevs@layer themevs:root - When should I use
*and when should I use:root, :hostas the parent selector? - How
@variant darkcan be combined with@themein a CSS-first configuration to override dark mode colors - Should I use
@themeor@theme inline? - How can I safely introduce the use of
light-dark()without increasing the minimum browser version requirement?
Disable dark mode, use light: instead of dark::
- TailwindCSS: How to do a light mode only modification?
- How to disable dark mode in TailwindCSS 4
light-dark() CSS function
With light-dark(), a color can be tied to the color-scheme. Based on the color-scheme, the browser selects the color corresponding to the current scheme. This essentially simplifies the declaration of light and dark themes, allowing it to be done in a single place, within @theme.
@theme {
--color-pink: light-dark(#eb6bd8, #8e0d7a);
--color-tsunami: light-dark(#77b4ea, #0d84ec);
}
Attention: TailwindCSS is shipped under the hood with LightningCSS. While LightningCSS does provide a fallback for older browsers to replace light-dark(), TailwindCSS does not apply it due to an open issue, meaning it is not available. TailwindCSS officially guarantees support starting from Baseline 2023, but light-dark() is a CSS function that was only stably introduced in Baseline 2024. This means that if you use it, your minimum browser version requirements will rise to the level where light-dark() was introduced. More details:
- https://tailwindcss.com/docs/compatibility (Baseline 2023)
- https://caniuse.com/?search=light-dark (Baseline 2024)
- Question for
light-dark()using in older browsers (In the question, the functionality is explained with many references and version numbers)
document.querySelector('button').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
<script src="https://unpkg.com/@tailwindcss/browser"></script>
<style type="text/tailwindcss">
/* changed the behavior of dark: (default: based on prefers-color-scheme) to work based on the presence of the .dark parent class */
@custom-variant dark (&:where(.dark, .dark *));
/* WARNING: This way, instead of Chrome 111, at least Chrome 123 will be required, and instead of Safari 16.4, Safari 17.5 will be needed for it to work. */
@theme {
--color-pink: light-dark(#eb6bd8, #8e0d7a);
--color-tsunami: light-dark(#77b4ea, #0d84ec);
}
* {
color-scheme: light; /* apply "light" (first) color from light-dark() */
@variant dark {
color-scheme: dark; /* apply "dark" (second) color from light-dark() */
}
}
</style>
<button class="size-20 bg-pink dark:text-white">Click Here</button>
<div class="w-50 h-12 bg-tsunami dark:text-white">
Lorem Ipsum
</div>
var(--tw-light, ...) var(--tw-dark, ...) alternative
The previously mentioned question has an answer that explains how the behavior of light-dark() can be replicated with a little extra work. This way, you can avoid raising the minimum browser version requirement in your project.
- How can I safely introduce the use of
light-dark()without increasing the minimum browser version requirement?
@theme inline {
--color-pink: var(--tw-light, #eb6bd8) var(--tw-dark, #8e0d7a);
--color-tsunami: var(--tw-light, #77b4ea) var(--tw-dark, #0d84ec);
}
Note: Since @theme ships the given value into a global variable, the value cannot be a variable itself; otherwise, CSS cannot properly track the fallback values. Therefore, you should always use @theme inline. @theme inline does not embed the values into a global variable. However, it isn't necessary, since later on we don't want to override the color for other themes, as each theme can be declared locally in a single line.
document.querySelector('button').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
<script src="https://unpkg.com/@tailwindcss/browser"></script>
<style type="text/tailwindcss">
/* changed the behavior of dark: (default: based on prefers-color-scheme) to work based on the presence of the .dark parent class */
@custom-variant dark (&:where(.dark, .dark *));
/* NOTE: It still works starting from Chrome 111 and Safari 16.4. */
@theme inline {
--color-pink: var(--tw-light, #eb6bd8) var(--tw-dark, #8e0d7a);
--color-tsunami: var(--tw-light, #77b4ea) var(--tw-dark, #0d84ec);
}
* {
color-scheme: light;
--tw-light: initial;
--tw-dark: ;
@variant dark {
color-scheme: dark;
--tw-light: ;
--tw-dark: initial;
}
}
</style>
<button class="size-20 bg-pink dark:text-white">Click Here</button>
<div class="w-50 h-12 bg-tsunami dark:text-white">
Lorem Ipsum
</div>
I have spent the last hour trying to figure this out and got nowhere. Any and all help is appreciated!
const colors = require("tailwindcss/colors");
module.exports = {
theme: {
colors: {
brand: colors.blue,
neutral: colors.slate,
error: colors.red,
success: colors.green,
warning: colors.yellow,
},
},
};V4 doesn't use tailwind.config.js anymore, they use @ theme in the main CSS file
I had too many colors to change from HEX to OKLCH
So, I made a tool to convert all my custom colors in one click
1-click = Done ✅