Using light-dark()
would be appealing to me, but it is part of the 2024 baseline and somewhat raises the minimum browser version requirement set by TailwindCSS v4 - which is already high - originally targeting the 2023 baseline.
Required minimum browser versions:
Tailwind CSS v4 (without light-dark() ) |
with light-dark() |
|
---|---|---|
Chrome 111 | Chrome 123 | (+) |
Safari 16.4 | Safari 17.5 | (+) |
Firefox 128 | Firefox 128 | (=) |
The LightningCSS engine that TailwindCSS v4 uses under the hood provides a solution for a polyfill-like replacement of light-dark()
with some extra manual code.
However, for compatibility reasons, TailwindCSS has simply disabled the use of this feature here.
tailwindlabs/tailwindcss
issue #15438 - [v4] light-dark is broken in optimized buildHow can I still use light-dark()
without increasing the minimum browser version requirement, even without relying on this feature?
document.querySelector('button').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-primary: light-dark(#da373d, #fd96b0);
}
* {
color-scheme: light; /* apply "light" (first) color from light-dark() */
@variant dark {
color-scheme: dark; /* apply "dark" (second) color from light-dark() */
}
}
</style>
<h1 class="m-4 text-primary text-3xl font-bold underline text-clifford">
Hello world!
</h1>
<button class="m-4 px-4 py-2 bg-sky-700 hover:bg-sky-950 text-sky-50 rounded-md cursor-pointer">Toggle Light/Dark</button>
It works, but for the reasons detailed in the question, it increases the minimum browser version requirements.
For the reasons described above, the code snippet only works on Chrome 123+ and Safari 17.5+. However, I'd like an alternative so that I don't have to target these versions, but instead align with the Chrome 111+ and Safari 16.4+ versions preferred by v4.
The goal is to be able to declare the light and dark color-scheme
values in a single line, similar to light-dark()
, so that both can be seen at once in one line.
Note: I like the light-dark()
solution, but I don't want to impose Baseline 2024 browser requirements on my project, as this could potentially cause me to lose visitors.
By ignoring LightningCSS's built-in polyfill, the solution can be implemented manually using --tw-light
and --tw-dark
variables. For each variable value not currently in use, assign the empty
value for color-scheme
; the variable that is active should be set to an initial
value so that its fallback color is ultimately applied as the primary
color. This approach requires using @variant dark
along with a custom @custom-variant dark
manual override.
document.querySelector('button').addEventListener('click', () => {
document.documentElement.classList.toggle('dark');
});
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@custom-variant dark (&:where(.dark, .dark *));
@theme inline {
--color-primary: var(--tw-light, #da373d) var(--tw-dark, #fd96b0);
}
* {
color-scheme: light;
--tw-light: initial;
--tw-dark: ;
@variant dark {
color-scheme: dark;
--tw-light: ;
--tw-dark: initial;
}
}
</style>
<h1 class="m-4 text-primary text-3xl font-bold underline text-clifford">
Hello world!
</h1>
<button class="m-4 px-4 py-2 bg-sky-700 hover:bg-sky-950 text-sky-50 rounded-md cursor-pointer">Toggle Light/Dark</button>
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.
Required minimum browser versions:
Tailwind CSS v4 | with var(--tw-light, ...) var(--tw-dark, ...) |
|
---|---|---|
Chrome 111 | Chrome 111 | (=) |
Safari 16.4 | Safari 16.4 | (=) |
Firefox 128 | Firefox 128 | (=) |
The advantage of light-dark()
is that it would work without a custom dark declaration, relying solely on the proper color-scheme
setting. However, to maintain the minimum browser version, this must be implemented manually using CSS variables.