csstailwind-csstailwind-css-4lightningcss

How can I safely introduce the use of `light-dark()` without increasing the minimum browser version requirement?


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.

How 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.


Solution

  • 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.