DEV Community

Cover image for Tailwind css dark mode switch with JavaScript
Sinan
Sinan

Posted on • Updated on • Originally published at Medium

Tailwind css dark mode switch with JavaScript

Tailwind css is really a greate utility-first framework that provides a lot of preset values (colors, sizes, etc...) that work very well out of the box. I also like the fact that I dont have to jump between the html and the css file, while others prefer the separation.

Now the problem is that tailwind makes it harder to implement a dark or a colored version, unless you know how it is done. Without tailwind I would add a class like 'scheme-dark' to the html tag and customize every element in my scss file like this:

/* style.scss */

h1 {
    color: black;
}

.scheme-dark {
    h1 {
        color: white;
    }
}
Enter fullscreen mode Exit fullscreen mode

However in tailwind we define the color of the text with a class in the html file, so this is what we want:

/* index.html */

<h1 class="text-blue-900 dark:text-white">Hello world!</h1>
Enter fullscreen mode Exit fullscreen mode

The official documentation recommends to add the following to the tailwind config

/* tailwind.config.js */

module.exports = {
  theme: {
    extend: {
      screens: {
        'dark': {'raw': '(prefers-color-scheme: dark)'},
        // => @media (prefers-color-scheme: dark) { ... }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This works, but not as good as expected. Why? Because the media rule prefers-color-scheme looks at the browser setting, it is not possible to change it with e.g. a button and some javascript. So the user would have to go into the browser settings and change to light/dark mode.

To give the user the option to change to light/dark or any other color mode, we can modify the tailwind configs.

First we create our custom variant by adding a new plugin in the tailwind configs:

    ...
    plugins: [
        plugin(function ({ addVariant, prefix }) {
            addVariant('dark', ({ modifySelectors, separator}) => {
                modifySelectors(({ selector }) => {
                    return selectorParser((selectors) => {
                        selectors.walkClasses((sel) => {
                            sel.value = `dark${separator}${sel.value}`
                            sel.parent.insertBefore(sel, selectorParser().astSync('.scheme-dark '))
                        })
                    }).processSync(selector)
                })
            })
        })
    ]
    ...
Enter fullscreen mode Exit fullscreen mode

The name of our variant is dark and it has a parent class .scheme-dark (don't forget the space at the end!)? This will be used by tailwind when it generates the css.

Then we add our custom variant to the properties that we want to use:

    ...
    variants: {
        textColor: ['dark', 'responsive', 'hover', 'focus'],
        backgroundColor: ['dark', 'responsive', 'hover', 'focus']
    },
    ...
Enter fullscreen mode Exit fullscreen mode

Tailwind will now generate every text color class and background color class additionally with the .dark:\ prefix with the parent class .scheme-dark. So e.g. for the text color text-white it will create the following css:

.text-white {
    color: #fff;
}

.scheme-dark .dark:\text-white {
    color: #fff;
}
Enter fullscreen mode Exit fullscreen mode

So we can now simply add the scheme-dark to our html tag and define a text/background color like <h1 class="text-black dark:text-white" >Hello</h1> when dark mode is enabled.

<script>
    const html = document.getElementsByTagName('html')[0];    

    function toggleDarkMode() {
        if(html.classList.contains('scheme-dark')) {
            html.classList.remove('scheme-dark');
        } else {
            html.classList.add('scheme-dark');
        }
    }
</script>

<button onclick="toggleDarkMode()">Toggle dark mode</button>
Enter fullscreen mode Exit fullscreen mode

Here is the complete tailwind config file:

const plugin = require("tailwindcss/plugin");
const selectorParser = require("postcss-selector-parser");

module.exports = {
    theme: {
    ...
    },
    variants: {
        textColor: ['dark', 'responsive', 'hover', 'focus'],
        backgroundColor: ['dark', 'responsive', 'hover', 'focus']
    },
    plugins: [
        plugin(function ({ addVariant, prefix }) {
            addVariant('dark', ({ modifySelectors, separator}) => {
                modifySelectors(({ selector }) => {
                    return selectorParser((selectors) => {
                        selectors.walkClasses((sel) => {
                            sel.value = `dark${separator}${sel.value}`
                            sel.parent.insertBefore(sel, selectorParser().astSync(prefix('.scheme-dark ')))
                        })
                    }).processSync(selector)
                })
            })
        })
    ]
}
Enter fullscreen mode Exit fullscreen mode

Now you might ask me: What If I want to change the color when hovering the text in dark mode?

No problemo amigo! Justo addo uno plugino:

        plugin(function ({ addVariant, e }) {
            addVariant('dark-hover', ({ modifySelectors, separator}) => {
                modifySelectors(({ className }) => {
                    return `.scheme-dark .${e(`dark\:hover${separator}${className}`)}:hover`
                })
            })
        })
Enter fullscreen mode Exit fullscreen mode

and add the variant:

    variants: {
        textColor: ['responsive', 'dark', 'dark-hover', 'hover', 'focus'],
    },
Enter fullscreen mode Exit fullscreen mode

Now we can do this:

<h1 class="text-black dark:text-white dark:hover:text-red-600 hover:text-blue-600">Hover me</h1>
Enter fullscreen mode Exit fullscreen mode

Remember, this is only the dark mode, you could also do the same for colored versions!

If you use postcss to remove unused css (recommended!) like

module.exports = {
    purge: [
        './build/app/views/**/*.php',
        './build/public/**/*.php',
    ],
    ...
}
Enter fullscreen mode Exit fullscreen mode

then you need to add an empty div with the class scheme-dark:

<div class="scheme-dark"></div>
```


If you don't do this every `scheme-dark` class will be removed!
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
begonaalvarezd profile image
Begoña Álvarez de la Cruz

Question! Is it possible to use these custom variants with @apply? I am developing some reusable components that need the use of @apply because otherwise the code gets out of hand. So far I am using something like:

:global(.scheme-dark) { // need global because I am using Svelte
        button {
            ...
            &.secondary {
                @apply bg-black;
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

But I would LOVE to have something like:

button {
            ...
            &.secondary {
                @apply bg-white;
                &:dark {
                   @apply bg-black;
                }
            }
        }
Enter fullscreen mode Exit fullscreen mode

Is this even possible? Thanks!

Collapse
 
begonaalvarezd profile image
Begoña Álvarez de la Cruz • Edited

No problemo amigo! Justo addo uno plugino

This made my day xD

Collapse
 
sinandev profile image
Sinan

Btw the same method can be used to create more functionality like dark mode focus etc...