├── .gitignore ├── README.md ├── index.js ├── package-lock.json ├── package.json └── tests ├── jit.test.js ├── prefix.test.js └── pseudoClasses.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This Tailwind CSS plugin adds variants for multiple themes, making it easy to add multiple color themes to your website. 2 | 3 | To use, install the plugin using the following command. 4 | 5 | ``` 6 | # using npm 7 | npm install tailwind-themes 8 | 9 | # using yarn 10 | yarn add tailwind-themes 11 | ``` 12 | 13 | Then, add the plugin to your Tailwind configuration, for example: 14 | 15 | ``` 16 | plugins: [ 17 | require("tailwind-themes")({ 18 | themes: { 19 | blue: ".blue", 20 | purple: ".theme-purple", 21 | }, 22 | }), 23 | ] 24 | ``` 25 | 26 | In the `themes` object, each key is the name of the variant (so a key of `blue` would create classes such as `blue:text-blue-900`) and each value is the selector that activates the variant (a value of `".theme-purple"` means that the purple variant will be activated if a class of `.theme-purple` exists earlier in the HTML tree). 27 | 28 | If you are using JIT mode, no other configuration is needed. For the default AOT mode, you should add the `themes` variant to the utilities you want to enable it for. An example is shown below, where the `themes` variant is enabled for color related utilities. 29 | 30 | ``` 31 | // only necessary if not using JIT mode 32 | variants: { 33 | extend: { 34 | backgroundColor: ["themes"], 35 | gradientColorStops: ["themes"], 36 | borderColor: ["themes"], 37 | divideColor: ["themes"], 38 | placeholderColor: ["themes"], 39 | textColor: ["themes"], 40 | }, 41 | }, 42 | ``` 43 | 44 | For an example of how this can be used, see the [demo](https://github.com/maggie-j-liu/tailwind-themes-demo). 45 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const plugin = require("tailwindcss/plugin"); 2 | 3 | module.exports = plugin.withOptions(({ themes }) => { 4 | return ({ addVariant }) => { 5 | for (const [name, sel] of Object.entries(themes)) { 6 | addVariant(name, `${sel} &`); 7 | } 8 | }; 9 | }); 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwind-themes", 3 | "version": "1.0.2", 4 | "description": "A Tailwind CSS plugin that adds variants for multiple color themes.", 5 | "homepage": "https://github.com/maggie-j-liu/tailwind-themes", 6 | "bugs": "https://github.com/maggie-j-liu/tailwind-themes/issues", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/maggie-j-liu/tailwind-themes" 10 | }, 11 | "main": "index.js", 12 | "files": [ 13 | "index.js" 14 | ], 15 | "scripts": { 16 | "test": "jest" 17 | }, 18 | "peerDependencies": { 19 | "tailwindcss": "^3.0.0" 20 | }, 21 | "devDependencies": { 22 | "jest": "^27.0.6", 23 | "jest-matcher-css": "^1.1.0", 24 | "postcss": "^8.3.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/jit.test.js: -------------------------------------------------------------------------------- 1 | const plugin = require("../index.js"); 2 | const postcss = require("postcss"); 3 | const tailwindcss = require("tailwindcss"); 4 | const cssMatcher = require("jest-matcher-css"); 5 | const path = require("path"); 6 | const fs = require("fs/promises"); 7 | const pseudoClasses = require("./pseudoClasses"); 8 | 9 | expect.extend({ 10 | toMatchCss: cssMatcher, 11 | }); 12 | 13 | const baseTheme = { 14 | purge: ["./jit.test.html"], 15 | theme: { 16 | colors: { 17 | blue: { 18 | 500: "#3B82F6", 19 | }, 20 | }, 21 | }, 22 | corePlugins: ["textColor"], 23 | }; 24 | 25 | const baseOptions = { 26 | themes: { blue: ".theme-blue" }, 27 | }; 28 | 29 | const html = (className) => ` 30 | 31 | 32 |
33 | 34 | 35 | 36 | `; 37 | 38 | const generatePluginCss = (config = baseTheme, options = baseOptions) => { 39 | return postcss( 40 | tailwindcss({ 41 | corePlugins: false, 42 | plugins: [plugin(options)], 43 | ...config, 44 | }) 45 | ) 46 | .process("@tailwind utilities;", { 47 | from: undefined, 48 | }) 49 | .then((result) => result.css); 50 | }; 51 | 52 | describe("works", () => { 53 | it("generates variants", async () => { 54 | await fs.writeFile("./jit.test.html", html(`blue:text-blue-500`)); 55 | const css = await generatePluginCss(); 56 | await fs.unlink("./jit.test.html"); 57 | expect(css).toMatchCss(` 58 | .theme-blue .blue\\:text-blue-500 { 59 | color: #3B82F6 60 | } 61 | `); 62 | }); 63 | 64 | for (const [variant, pseudoClass] of Object.entries(pseudoClasses)) { 65 | it(`generates ${variant} variants`, async () => { 66 | await fs.writeFile( 67 | `./jit-${variant}.test.html`, 68 | html(`blue:${variant}:text-blue-500`) 69 | ); 70 | const css = await generatePluginCss({ 71 | ...baseTheme, 72 | purge: [`./jit-${variant}.test.html`], 73 | }); 74 | await fs.unlink(`./jit-${variant}.test.html`); 75 | expect(css).toMatchCss(` 76 | .theme-blue .blue\\:${variant}\\:text-blue-500:${pseudoClass} { 77 | color: #3B82F6 78 | } 79 | `); 80 | }); 81 | } 82 | }); 83 | -------------------------------------------------------------------------------- /tests/prefix.test.js: -------------------------------------------------------------------------------- 1 | const plugin = require("../index.js"); 2 | const postcss = require("postcss"); 3 | const tailwindcss = require("tailwindcss"); 4 | const cssMatcher = require("jest-matcher-css"); 5 | const path = require("path"); 6 | const fs = require("fs/promises"); 7 | const pseudoClasses = require("./pseudoClasses"); 8 | 9 | expect.extend({ 10 | toMatchCss: cssMatcher, 11 | }); 12 | 13 | const baseTheme = { 14 | purge: ["./jit.test.html"], 15 | theme: { 16 | colors: { 17 | blue: { 18 | 500: "#3B82F6", 19 | }, 20 | }, 21 | }, 22 | prefix: "tw-", 23 | corePlugins: ["textColor"], 24 | }; 25 | 26 | const baseOptions = { 27 | themes: { blue: ".theme-blue" }, 28 | }; 29 | 30 | const html = (className) => ` 31 | 32 | 33 | 34 | 35 | 36 | 37 | `; 38 | 39 | const generatePluginCss = (config = baseTheme, options = baseOptions) => { 40 | return postcss( 41 | tailwindcss({ 42 | corePlugins: false, 43 | plugins: [plugin(options)], 44 | ...config, 45 | }) 46 | ) 47 | .process("@tailwind utilities;", { 48 | from: undefined, 49 | }) 50 | .then((result) => result.css); 51 | }; 52 | 53 | describe("works with prefixes", () => { 54 | it("generates variants", async () => { 55 | await fs.writeFile("./jit.test.html", html(`blue:tw-text-blue-500`)); 56 | const css = await generatePluginCss(); 57 | await fs.unlink("./jit.test.html"); 58 | expect(css).toMatchCss(` 59 | .tw-theme-blue .blue\\:tw-text-blue-500 { 60 | color: #3B82F6 61 | } 62 | `); 63 | }); 64 | 65 | for (const [variant, pseudoClass] of Object.entries(pseudoClasses)) { 66 | it(`generates ${variant} variants`, async () => { 67 | await fs.writeFile( 68 | `./jit-${variant}.test.html`, 69 | html(`blue:${variant}:tw-text-blue-500`) 70 | ); 71 | const css = await generatePluginCss({ 72 | ...baseTheme, 73 | purge: [`./jit-${variant}.test.html`], 74 | }); 75 | await fs.unlink(`./jit-${variant}.test.html`); 76 | expect(css).toMatchCss(` 77 | .tw-theme-blue .blue\\:${variant}\\:tw-text-blue-500:${pseudoClass} { 78 | color: #3B82F6 79 | } 80 | `); 81 | }); 82 | } 83 | }); 84 | -------------------------------------------------------------------------------- /tests/pseudoClasses.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hover: "hover", 3 | focus: "focus", 4 | first: "first-child", 5 | last: "last-child", 6 | visited: "visited", 7 | checked: "checked", 8 | "focus-within": "focus-within", 9 | "focus-visible": "focus-visible", 10 | active: "active", 11 | disabled: "disabled", 12 | odd: "nth-child(odd)", 13 | even: "nth-child(even)", 14 | }; 15 | --------------------------------------------------------------------------------