JNavith / tailwindcss-theme-variants

Media-query- or JavaScript-based theme variants with fallback for Tailwind CSS
https://tailwindcss-theme-variants.web.app/
MIT License
186 stars 5 forks source link

Using both selectors and media-queries not working as expected. #24

Open mia-riezebos opened 3 weeks ago

mia-riezebos commented 3 weeks ago

In the docs on the website, there's a table specifying that if a selector matches, it takes precedence over any media query.

match neither prefers-color-scheme: light prefers-color-scheme: dark
neither none cyan navy
.cyan cyan cyan cyan
.navy navy navy navy

As previously noted, when a required selector is present, it takes precendence over the media queries. Stated another way, the media queries only matter when no selector matches.

The thing is, in my project, my themes are not behaving as described above.

My config is like such:

// tailwind.config.cjs
// ...
    variants: {
        extend: {
            backgroundColor: ['schemes'],
            textColor: ['schemes'],
            fill: ['schemes'],
            stroke: ['schemes'],
            borderColor: ['schemes'],
            outlineColor: ['schemes'],
            ringColor: ['schemes'],
            ringOffsetColor: ['schemes'],
            boxShadow: ['schemes']
        }
    },
    plugins: [
        // ...
        // Color Schemes
        themeVariants({
            group: 'schemes',
            themes: {
                // Basic Dark / Light Themes
                dark: {
                    mediaQuery: prefersDark,
                    selector: "[data-theme='dark']",
                },

                light: {
                    mediaQuery: prefersLight,
                    selector: "[data-theme='light']",
                }

                // // Inverted Colors (make sure there's no compatibility issues with inverted colors)
                // 'colors-inverted': {
                //  mediaQuery: colorsInverted
                // }
            },
            // baseSelector: 'html',
            // fallback: 'dark'
        }),
        // ...
    ]

The generated CSS (for example on the light theme), looks like this:

@media (prefers-color-scheme: light){
        :root[data-theme='light'] .light\:bg-other-green-300{
                --tw-bg-opacity: 1;
                background-color: rgb(134 239 172 / var(--tw-bg-opacity));
        }
}

and behaves as you would expect from the generated css; BOTH the media query AND the selector [data-theme='light'] have to match for the background to turn green.

JNavith commented 3 weeks ago

Thanks for the report! I can reproduce this. I'm going to try my best to fix this, but Tailwind CSS and related has changed a lot since my last update to this project, and I haven't been working with JavaScript lately, so it might be a struggle and I might just have to post a snippet of code that bypasses this plugin that you can use in case I can't update this project without breakage. We'll see, but here's hoping.

JNavith commented 3 weeks ago

This is happening because recent versions of Tailwind CSS reserve special meaning for the dark variant. Even though it's unfortunate that this can't be disabled, they at least allow it to be customized. So, as long as I'm right to assume you are using version 3 (e.g. 3.4.4 is what I'm testing on), you can trim out this plugin and use an easier and terser approach:

// possibly const plugin = require("tailwindcss/plugin"); instead, but you can switch from .cjs to .js and use ESM syntax in modern Tailwind versions
import plugin from 'tailwindcss/plugin';

/** @type {import('tailwindcss').Config} */
// possibly module.exports = instead, but you can switch from .cjs to .js and use ESM syntax in modern Tailwind versions
export default {
    darkMode: ['variant', ['@media (prefers-color-scheme: dark) { :root & }', ':root[data-theme=dark] &']],

    theme: {
                // ... your customizations here
        extend: {}
    },
        // variants: section no longer needed in v3
    plugins: [
        plugin(({ addVariant }) => {
            addVariant('light', ['@media (prefers-color-scheme: light) { :root & }', ':root[data-theme=light] &']);
        })
    ]
}

This works for your current configuration without having to refactor the HTML.

A note on potential future needs, though: because their dark variant will only appear after the light variant in the CSS, it wouldn't be possible to have dark as the fallback theme without raising the specificity in the light variant if it comes to that.

JNavith commented 3 weeks ago

An alternative option that takes more work to implement:

It is also an option not to use the built-in darkMode, but disabling it is not possible, so you'd have to rename the theme variant so that it doesn't clash:

// if you continue to use my plugin
themes: {
        // example names
    "dark-theme": { ... },
    "light-theme": { ... }
}

// if you use the approach bypassing this plugin
addVariant('theme-dark', ['@media (prefers-color-scheme: dark) { :root & }', ':root[data-theme=dark] &']);
addVariant('theme-light', ['@media (prefers-color-scheme: light) { :root & }', ':root[data-theme=light] &']);

and go back and change all occurrences of light: and dark: in the HTML (youch - why I wouldn't go through with this option except if you know you need a capability that the built-in darkMode customization can't provide (e.g. fallback)) to their new variant names.

JNavith commented 3 weeks ago

Implementing fallback without my plugin would be done by changing the order of the addVariant calls to change the order they appear in the CSS, and writing :not calls like this to replicate how my plugin would've done it:

// theme-dark will apply as long as the light theme hasn't been activated by `prefers-color-scheme: light` or by `[data-theme=light]`
addVariant('theme-dark', ':root &');            
// but this can override it (it will have enough specificity in the CSS)
addVariant('theme-light', [
    '@media (prefers-color-scheme: light) { :root:not([data-theme=dark]) & }',
    '[data-theme=light] &'
]);

(where here, and in the messages before this, :root is the base selector)

JNavith commented 3 weeks ago

Regardless of these workarounds, this plugin and its documentation need to be updated to address this issue. Thanks again for the report.

mia-riezebos commented 3 weeks ago

Disabling darkMode in the tailwind config is actually possible using darkMode: false. I ran into this with Supermaven and it's not documented, but it does work. This will have the plugin generate the dark: variant properly again, but the generated styles, even for light mode don't match the described behaviour.

thanks for looking into this!

mia-riezebos commented 3 weeks ago

I quite like the syntax and provided media query exports of this plugin, and would enjoy continued use of it!

At this time, I am circumventing the issue by adding two auto themes

        // Color Schemes
        themeVariants({
            group: 'schemes',
            themes: {
                // Basic Dark / Light Themes
                dark: {
                    selector: ":is([data-theme='dark'], :has(>[data-theme='dark']))"
                },

                light: {
                    selector: ":is([data-theme='light'], :has(>[data-theme='light']))"
                },

                'auto-light': {
                    selector: ":is([data-theme='auto'], :has([data-theme='auto']))",
                    mediaQuery: prefersLight
                },

                'auto-dark': {
                    selector: ":is([data-theme='auto'], :has([data-theme='auto']))",
                    mediaQuery: prefersDark
                }

                // // Inverted Colors (make sure there's no compatibility issues with inverted colors)
                // 'colors-inverted': {
                //  mediaQuery: colorsInverted
                // }
            },
            baseSelector: '*'
            // fallback: 'auto-dark'
        }),

It is a little... verbose, but it was the solution I came up with the quickest, which is what mattered at this time. The main annoyance is providing all theme variants whenever I style an element.

again, thanks for getting back to me, I'll give the built-in variant config a try.

mia-riezebos commented 3 weeks ago

Ok so I like your workaround approach but I did still wanna keep the syntax from your plugin sooo

@mia-cx/tailwindcss-themes

I will add attribution in the readme in the next commit, there's already a little comment at the top of the index file

mia-riezebos commented 3 weeks ago

Seen as this repo seems stale, I would be down to maintain a spiritual successor of this plugin. I don't see a future where I stop working in ts/js soon.

Let me know!