New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

tailwindcss-theme-variants

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tailwindcss-theme-variants

JavaScript- or media-query-based theme variants with fallback for Tailwind CSS

  • 0.6.0
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
938
decreased by-22.86%
Maintainers
1
Weekly downloads
 
Created
Source

This Tailwind CSS plugin registers variants for theming without needing custom properties. It has support for responsive variants, extra stacked variants, media queries, and falling back to a particular theme when none matches.

You are recommended to check out the comparison table of all Tailwind CSS theming plugins below before committing to one.

Installation

npm install --save-dev tailwindcss-theme-variants

Basic usage

Using selectors to choose the active theme

With this Tailwind configuration,

const { tailwindcssThemeVariants } = require("tailwindcss-theme-variants");

module.exports = {
    theme: {
        backgroundColor: {
            "gray-900": "#1A202C",
        },
    },

    variants: {
        backgroundColor: ["light", "dark"],
    },

    plugins: [
        tailwindcssThemeVariants({
            themes: {
                light: {
                    selector: ".light-theme",
                },
                dark: {
                    selector: ".dark-theme",
                },
            },
        }),
    ],
};

this CSS is generated:

.bg-gray-900 {
    background-color: #1A202C;
}

:root.light-theme .light\:bg-gray-900 {
    background-color: #1A202C;
}

:root.dark-theme .dark\:bg-gray-900 {
    background-color: #1A202C;
}

💡 You can choose more than just classes for your selectors. Other, good options include data attributes, like [data-padding=compact]. You can go as crazy as .class[data-theme=light]:dir(rtl), for example, but at that point you need to be careful with specificity!

After also enabling "light" and "dark" variants for textColor and bringing in more colors from the default palette, we can implement a simple themed button in HTML like this:

<html class="light-theme"> <!-- Change to dark-theme -->
    <button class="light:bg-teal-200 dark:bg-teal-800 light:text-teal-700 dark:text-teal-100">
        Sign up
    </button>
</html>

This will result in dark text on a light background in the light theme, and light text on a dark background in the dark theme.

Using media queries to choose the active theme

You may rather choose to tie your theme selection to matched media queries, like prefers-color-scheme:

const { tailwindcssThemeVariants, prefersLight, prefersDark } = require("tailwindcss-theme-variants");

module.exports = {
    theme: {
        backgroundColor: {
            "teal-500": "#38B2AC",
        },
    },

    variants: {
        backgroundColor: ["light", "dark"],
    },

    plugins: [
        tailwindcssThemeVariants({
            themes: {
                light: {
                    mediaQuery: prefersLight /* "@media (prefers-color-scheme: light)" */,
                },
                dark: {
                    mediaQuery: prefersDark /* "@media (prefers-color-scheme: dark)" */,
                },
            },
        }),
    ],
};

Which generates this CSS:

.bg-teal-500 {
    background-color: #38B2AC
}

@media (prefers-color-scheme: light) {
    .light\:bg-teal-500 {
        background-color: #38B2AC;
    }
}

@media (prefers-color-scheme: dark) {
    .dark\:bg-teal-500 {
        background-color: #38B2AC;
    }
}

💡 Keep the variants listed in the same order as in themes in this plugin's configuration for consistency and the most expected behavior. In backgroundColor's variants, light came first, then dark, so we also list light before dark in tailwindcssThemeVariants's themes option.

Full configuration

This plugin expects configuration of the form

{
    themes: {
        [name: string]: {
            // At least one is required
            selector?: string;
            mediaQuery?: string;
        }
    };

    baseSelector?: string;
    fallback?: string | boolean;

    variants?: {
        // The name of the variant -> what has to be done to the selector for the variant to be active
        [name: string]: (selector: string) => string;
    };
}

Where each parameter means:

  • themes: an object mapping a theme name to the conditions that determine whether or not the theme will be active.

    • selector: a selector that has to be active on baseSelector for this theme to be active. For instance, if baseSelector is html, and themes.light's selector is .light-theme, then the light theme's variant(s) will be in effect whenever html has the light-theme class on it.

    • mediaQuery: a media query that has to be active for this theme to be active. For instance, if the reduced-motion theme has mediaQuery "@media (prefers-reduced-motion: reduce)" (importable as prefersReducedMotion), then the reduced-motion variant(s) will be active.

  • baseSelector (default ":root" if you use any selectors to activate themes, otherwise ""): the selector that each theme's selector will be applied to to determine the active theme.

  • fallback (default false): chooses a theme to fall back to when none of the media queries or selectors are active. You can either manually select a theme by giving a string like "solarized-dark" or implicitly select the first one listed in themes by giving true.

  • variants (default is nothing): an object mapping the name of a variant to a function that gives a selector for when that variant is active.

    For example, the importable even variant takes a selector and returns `${selector}:nth-child(even)`. The importable groupHover (which you are recommended to name "group-hover" for consistency) variant returns `.group:hover ${selector}`

Examples

💡 At the time of writing, this documentation is a work in progress. For all examples, where I've done my best to stretch the plugin to its limits (especially towards the end of the file), see the test suite in tests/index.ts.

Fallback

Media queries

With the same media-query-activated themes as above,

themes: {
    light: {
        mediaQuery: prefersLight /* "@media (prefers-color-scheme: light)" */,
    },
    dark: {
        mediaQuery: prefersDark /* "@media (prefers-color-scheme: dark)" */,
    },
},

we can create a table to show what the active theme will be under all possible conditions:

Matching media queryNeitherprefers-color-scheme: lightprefers-color-scheme: dark
Active themeNonelightdark

The whole point of the fallback feature is to address that None case. It could mean that the visitor is using a browser that doesn't support prefers-color-scheme, such as IE11. Instead of leaving them on an unthemed site, we can "push" them into a particular theme by specifying fallback.

themes: {
    light: {
        mediaQuery: prefersLight /* "@media (prefers-color-scheme: light)" */,
    },
    dark: {
        mediaQuery: prefersDark /* "@media (prefers-color-scheme: dark)" */,
    },
},
// New addition
fallback: "light",

Which will change the generated CSS to activate light earlier than any media queries, so they can still override that declaration by being later in the file. You could think of light as the default theme in this case.

.bg-teal-500 {
    background-color: #38B2AC;
}

/* New addition */
.light\:bg-teal-500 {
    background-color: #38B2AC;
}
/* End new addition */

@media (prefers-color-scheme: light) {
    .light\:bg-teal-500 {
        background-color: #38B2AC;
    }
}

@media (prefers-color-scheme: dark) {
    .dark\:bg-teal-500 {
        background-color: #38B2AC;
    }
}

Which, in turn, changes the active theme table to:

Matching media queryNeitherprefers-color-scheme: lightprefers-color-scheme: dark
Active themelightlightdark

💡 Even though background-color has been used in every example, theme variants are available for any utility.

Selectors

💡 fallback also works for selector-activated themes, which would be useful for visitors without JavaScript enabled—if that's how your themes are selected.

themes: {
    light: {
        selector: ".light-theme",
    },
    dark: {
        selector: ".dark-theme",
    },
},
fallback: "dark",

This generates:

.bg-gray-900 {
    background-color: #1A202C;
}

:root.light-theme .light\:bg-gray-900 {
    background-color: #1A202C;
}

:root:not(.light-theme) .dark\:bg-gray-900 {
    background-color: #1A202C;
}

:root.dark-theme .dark\:bg-gray-900 {
    background-color: #1A202C;
}

Which has the active theme table:

Matching selectorActive theme
Neitherdark
:root.light-themelight
:root.dark-themedark

Stacked variants

💡 All of Tailwind CSS's core variants and more are bundled for use with this plugin. You can see the full list in src/variants.ts.

By specifying variants in this plugin's options, you can "stack" extra variants on top of the existing theme variants. (We call it stacking because there are multiple variants required, like in night:focus:border-white, the border will only be white if the night theme is active and the element is :focused on).

Here's an example of combining prefers-contrast: high (which you can import as prefersHighContrast) with the :hover variant (which you can import as hover):

themes: {
    light: {
        mediaQuery: prefersHighContrast /* "@media (prefers-contrast: high)" */,
    },
},
variants: {
    "hover": hover /* (selector) => `${selector}:hover` */,
},

You could create a simple card that uses contrast pleasant for fully sighted visitors, but intelligently switches to functional high contrast for those who specify it:

<div class="bg-gray-100 high-contrast:bg-white text-gray-800 high-contrast:text-black">
    <h1>Let me tell you all about...</h1>
    <h2>... this great idea I have!</h2>

    <a href="text-blue-500 high-contrast:text-blue-700 hover:text-blue-600 high-contrast:hover:text-blue-900">
        See more
    </a>
</div>

Responsive variants

Responsive variants let you distinguish the current breakpoint per theme, letting you say lg:green-theme:border-green-200 to have a green-200 border only when the breakpoint is lg (or larger) and the green-theme is active, for instance.

⚠️ Responsive variants are automatically generated whenever responsive is listed in the utility's variants in the Tailwind CSS configuration, not this plugin's configuration. Also, because this feature is provided by Tailwind CSS rather than this plugin, you have to specify breakpoint: before the theme-name: instead of after like in stacked variants).

const { tailwindcssThemeVariants } = require("tailwindcss-theme-variants");

module.exports = {
    theme: {
        // Your Tailwind CSS theme configuration
    },
    variants: {
        textColor: ["responsive", "day", "night"]
    },
    plugins: [
        tailwindcssThemeVariants({
            themes: {
                day: { selector: "[data-time=day]" },
                night: { selector: "[data-time=night]" },
            },
        }),
    ],
};

With this, we could make the landing page's title line change color at different screen sizes "within" each theme:

<h1 class="day:text-black          night:text-white
           sm:day:text-orange-800  sm:night:text-yellow-100
           lg:day:text-orange-600  lg:night:text-yellow-300">
    
    The best thing that has ever happened. Ever.
</h1>

We could also make a group of themes for data density, like you can configure in GMail:

const { tailwindcssThemeVariants } = require("tailwindcss-theme-variants");

module.exports = {
    theme: {
        // Your Tailwind CSS theme configuration
    },
    variants: {
        padding: ["responsive", "comfortable", "compact"]
    },
    plugins: [
        tailwindcssThemeVariants({
            themes: {
                comfortable: { selector: "[data-density=comfortable]" },
                compact: { selector: "[data-density=compact]" },
            },
            // Fall back to the first theme listed (comfortable) when density is not configured
            fallback: true,
        }),
    ],
};

This will allow us to configure the padding for each theme for each breakpoint, of a list of emails in the inbox (so original!):

<li class="comfortable:p-2     compact:p-0
           md:comfortable:p-4  md:compact:p-1
           xl:comfortable:p-6  xl:compact:p-2">
    
    FWD: FWD: The real truth behind...
</li>
Extra stacked variants

You can still stack extra variants even while using responsive variants.

TODO

Using both selectors and media queries

TODO

TODO: Show active theme tables for every example Such as:

MatchNeitherprefers-color-scheme: lightprefers-color-scheme: dark
NeitherNonecyannavy
:root.daycyancyancyan
:root.nightnavynavynavy

⚠️ If you are stacking more variants while using both selectors and media queries to define when themes should be active, then TODO:

Fallback

TODO

Call the plugin more than once for multiple groups

TODO

The ultimate example: how I use every feature together in production

TODO

Alternatives

TODO: theming plugin comparison table

Native screenstailwindcss-dark-modetailwindcss-darkmodetailwindcss-prefers-dark-modetailwindcss-theme-swappertailwindcss-theme-variantstailwindcss-theming
Controllable with selectors (classes or data attributes)
Responsive
Requires custom properties
Supports prefers-color-scheme: darkWith JavaScript
Supports prefers-color-scheme: lightWith JavaScript
Other thing

License and Contributing

MIT licensed. There are no contributing guidelines. Just do whatever you want to point out an issue or feature request and I'll work with it.


Repository preview image generated with GitHub Social Preview

Keywords

FAQs

Package last updated on 26 Jul 2020

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc