6

TailwindCSS recently released a major updated to version 4. One of the main changes is that it no longer uses a tailwind.config.js file, and instead all the configuration is managed through a single CSS file: TailwindCSS upgrade guide. Most of the time, the new configuration method is pretty straight forward. However, there's something I've been unable to do so far.

I usually want to completely disable dark mode. For this reason, I used to do this in the past in my configuration file:

module.exports = {
  darkMode: false, // Disable dark mode
  theme: {
    extend: {},
  },
  plugins: [],
}

These would completely ignore any dark: classes when compiling. According to the official documentation, dark mode now works using the CSS prefers-color-scheme media feature but there's no specific guidance in how to disable it.

I did find a sample to override the default functionality using this line in the new CSS configuration file: @custom-variant dark (&:where(.dark, .dark *));. Then, if I don't add the class dark up in the DOM tree, dark mode disabled but this feels like a clunky solution. When compiling the CSS file, all classes and styles for dark mode are still there, whereas my previous solution with the JavaScript configuration file omitted those.

What's the proper way to set up the new CSS configuration file to completely disable dark mode? I know that the logical solution would be to just remove any dark: classes, but I choose to work like this because I use some third part components and plugins that always force dark mode based on system preferences. Hence, my main website has no dark mode but the specific components do render in dark mode making it less visually pleasing. I appreciate any guidance.

Tailwind CSS' official playground could be used for testing. I've trying to use the following code for something simple:

<div class="bg-red-400 dark:bg-green-400 h-screen w-screen">
</div>

It's possible to review the generated CSS in the bottom left tab. Any code like this shouldn't be compiled with the appropriate configuration file:

.dark\:bg-green-400 {
  @media (prefers-color-scheme: dark) {
    background-color: var(--color-green-400);
  }
}

2
  • 1
    Could you simply not use any dark: variants in your template markup? Commented Mar 1 at 21:03
  • 1
    Unfortunately this isn't possible. I did remove all dark: classes from my HTML document. However, I also use third part plugins of TailwindCSS for some components such as date pickers. These have no option to disable dark mode so I used the global setting to force light mode for everything. Commented Mar 2 at 0:28

2 Answers 2

3

You can adjust the dark variant behavior using @custom-variant like this:

<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<style type="text/tailwindcss">
@custom-variant dark (&:where(.dark, .dark *));
</style>

<div class="bg-red-400 dark:bg-green-400 h-24 w-46">
  Light bg-red-400
</div>

<div class="dark">
  <div class="bg-red-400 dark:bg-green-400 h-24 w-46">
    Dark dark:bg-green-400
  </div>
</div>

This way, it no longer works based on the default prefers-color-scheme, but instead follows the .dark parent class. Of course, you can assign any other setting to it.

Generated CSS will be:

.bg-red-400 {
  background-color: var(--color-red-400);
}
.dark\:bg-green-400 {
  &:where(.dark, .dark *) {
    background-color: var(--color-green-400);
  }
}

So, if you want to disable it, simply override the default rule with the @custom-variant directive and optionally set a rule that never applies.

Related:

Other related:

Known issues

My solution works because dark mode will only activate manually. However, a class with a dark: variant still gets included in the production CSS. Once I learn more, I'll update the response on how to completely remove it from the production CSS.

Sign up to request clarification or add additional context in comments.

Comments

2

When specifying @custom-variant dark (&:where(.dark, .dark *)) the user or more commonly some dark mode extension can still add dark to root HTML which will cause the UI to look weird if you've some third party components that support dark mode but you don't. One hacky way to solve this is to use a selector that never matches:

@custom-variant dark (&:not(*));

Make sure to remove any custom .dark { ... } blocks that you might have (e.g., for CSS variables).


Haven't fully tested, but some postcss plugin like this can be used to remove such selectors from the CSS bundle:

import type { Plugin } from 'postcss'
import parser, { type Selector } from 'postcss-selector-parser'

export function postcssRemoveNotStar(): Plugin {
  return {
    postcssPlugin: 'postcss-remove-not-star',
    Once(root) {
      root.walkRules((rule) => {
        let removedAny = false
        const processed = parser((selectors) => {
          selectors.each((selector) => {
            if (selectorHasNotStar(selector)) {
              selector.remove()
              removedAny = true
            }
          })
        }).processSync(rule.selector)
        if (removedAny) {
          if (processed.trim() === '') {
            rule.remove()
          } else {
            rule.selector = processed
          }
        }
      })
    }
  }
}

function selectorHasNotStar(selector: Selector): boolean {
  let hasNotStar = false
  selector.walkPseudos((pseudo) => {
    if (pseudo.value === ':not' && pseudo.nodes?.length === 1) {
      const notNode = pseudo.nodes[0]
      if (
        notNode?.type === 'selector' &&
        notNode.nodes.length === 1 &&
        notNode.nodes[0]!.value === '*'
      ) {
        hasNotStar = true
      }
    }
  })
  return hasNotStar
}

PS: Perhaps the plugin can be modified to simply remove all selectors matching /^\.dark\b/, that way the custom-variant at-rule won't be needed.

2 Comments

I like the line of thought. It's a pity that modifying PostCSS requires PostCSS at all. In the case of a CLI or Vite plugin, this can significantly affect the project - of course, that's only if it bothers you that :not(*) ends up in the output.css.
@rozsazoltan Hmm, yeah but it works fine with vite. You can just copy paste and add it to css.postcss.plugins array in vite.config.ts. Someone might be able to write a plugin for lightningcss too if perf is the concern. For tailwind cli, I'm not sure, but most likely it will need a separate step after build.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.