├── .gitignore ├── .prettierignore ├── .prettierrc ├── dist ├── types │ └── index.d.ts ├── index.modern.js ├── index.cjs ├── index.module.js ├── index.umd.js ├── index.modern.js.map ├── index.cjs.map ├── index.umd.js.map └── index.module.js.map ├── tsconfig.json ├── .eslintrc.json ├── index.ts ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | README.md 2 | dist 3 | tsconfig.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "arrowParens": "always", 5 | "bracketSameLine": false, 6 | "printWidth": 120, 7 | "tabWidth": 2, 8 | "semi": true 9 | } 10 | -------------------------------------------------------------------------------- /dist/types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare const _default: { 2 | handler: import("tailwindcss/types/config").PluginCreator; 3 | config?: Partial | undefined; 4 | }; 5 | export default _default; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "skipLibCheck": true, 5 | "target": "es2022", 6 | "allowJs": true, 7 | "resolveJsonModule": true, 8 | "moduleDetection": "force", 9 | "isolatedModules": true, 10 | "strict": true, 11 | "noUncheckedIndexedAccess": true, 12 | "module": "NodeNext", 13 | "outDir": "dist", 14 | "sourceMap": true, 15 | "declaration": true, 16 | "lib": ["es2022"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 8 | "overrides": [], 9 | "parser": "@typescript-eslint/parser", 10 | "parserOptions": { 11 | "ecmaVersion": "latest", 12 | "sourceType": "module" 13 | }, 14 | "plugins": ["@typescript-eslint", "prettier"], 15 | "rules": { 16 | "prettier/prettier": 1, 17 | "@typescript-eslint/no-explicit-any": "off" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import plugin from 'tailwindcss/plugin.js'; 2 | 3 | const getStyleVarName = (modifier: string | null) => `--tw-signal${modifier ? `_${modifier}` : ''}`; 4 | // using empty values here so the compiler plays nice and generates the styles without values 5 | const EMPTY_VALUES = { values: { DEFAULT: '' } }; 6 | 7 | export default plugin(({ matchUtilities, matchVariant }) => { 8 | matchUtilities( 9 | { 10 | signal: (_, { modifier }) => { 11 | return { 12 | [getStyleVarName(modifier)]: 'true', 13 | }; 14 | }, 15 | }, 16 | { 17 | ...EMPTY_VALUES, 18 | modifiers: 'any', 19 | } 20 | ); 21 | 22 | matchVariant( 23 | 'signal', 24 | (_, { modifier }) => { 25 | return `@container style(${getStyleVarName(modifier)}: true)`; 26 | }, 27 | EMPTY_VALUES 28 | ); 29 | }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Brandon McConnell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/index.modern.js: -------------------------------------------------------------------------------- 1 | function e(){return e=Object.assign?Object.assign.bind():function(e){for(var t=1;t({}))){const n=function(n){return{__options:n,handler:e(n),config:t(n)}};return n.__isOptionsFunction=!0,n.__pluginFunction=e,n.__configFunction=t,n};const r=n}),r=t(function(e,t){function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});const i=/*#__PURE__*/r(n).default});const i=e=>"--tw-signal"+(e?`_${e}`:""),o={values:{DEFAULT:""}};var u=(0,(r.__esModule?r:{default:r}).default)(({matchUtilities:t,matchVariant:n})=>{t({signal:(e,{modifier:t})=>({[i(t)]:"true"})},e({},o,{modifiers:"any"})),n("signal",(e,{modifier:t})=>`@container style(${i(t)}: true)`,o)});export{u as default}; 2 | //# sourceMappingURL=index.modern.js.map 3 | -------------------------------------------------------------------------------- /dist/index.cjs: -------------------------------------------------------------------------------- 1 | function n(){return n=Object.assign?Object.assign.bind():function(n){for(var t=1;t({}))){const e=function(e){return{__options:e,handler:n(e),config:t(e)}};return e.__isOptionsFunction=!0,e.__pluginFunction=n,e.__configFunction=t,e};const r=e}),r=t(function(n,t){function r(n){return n&&n.__esModule?n:{default:n}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});const i=/*#__PURE__*/r(e).default}),i=function(n){return"--tw-signal"+(n?"_"+n:"")},o={values:{DEFAULT:""}},u=(0,(r.__esModule?r:{default:r}).default)(function(t){var e=t.matchVariant;(0,t.matchUtilities)({signal:function(n,t){var e;return(e={})[i(t.modifier)]="true",e}},n({},o,{modifiers:"any"})),e("signal",function(n,t){return"@container style("+i(t.modifier)+": true)"},o)});module.exports=u; 2 | //# sourceMappingURL=index.cjs.map 3 | -------------------------------------------------------------------------------- /dist/index.module.js: -------------------------------------------------------------------------------- 1 | function n(){return n=Object.assign?Object.assign.bind():function(n){for(var t=1;t({}))){const e=function(e){return{__options:e,handler:n(e),config:t(e)}};return e.__isOptionsFunction=!0,e.__pluginFunction=n,e.__configFunction=t,e};const r=e}),r=t(function(n,t){function r(n){return n&&n.__esModule?n:{default:n}}Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return i}});const i=/*#__PURE__*/r(e).default}),i=function(n){return"--tw-signal"+(n?"_"+n:"")},o={values:{DEFAULT:""}},u=(0,(r.__esModule?r:{default:r}).default)(function(t){var e=t.matchVariant;(0,t.matchUtilities)({signal:function(n,t){var e;return(e={})[i(t.modifier)]="true",e}},n({},o,{modifiers:"any"})),e("signal",function(n,t){return"@container style("+i(t.modifier)+": true)"},o)});export{u as default}; 2 | //# sourceMappingURL=index.module.js.map 3 | -------------------------------------------------------------------------------- /dist/index.umd.js: -------------------------------------------------------------------------------- 1 | !function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(n||self).tailwindcssSignals=e()}(this,function(){function n(){return n=Object.assign?Object.assign.bind():function(n){for(var e=1;e({}))){const t=function(t){return{__options:t,handler:n(t),config:e(t)}};return t.__isOptionsFunction=!0,t.__pluginFunction=n,t.__configFunction=e,t};const i=t}),i=e(function(n,e){function i(n){return n&&n.__esModule?n:{default:n}}Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return o}});const o=/*#__PURE__*/i(t).default}),o=function(n){return"--tw-signal"+(n?"_"+n:"")},r={values:{DEFAULT:""}};return(0,(i.__esModule?i:{default:i}).default)(function(e){var t=e.matchVariant;(0,e.matchUtilities)({signal:function(n,e){var t;return(t={})[o(e.modifier)]="true",t}},n({},r,{modifiers:"any"})),t("signal",function(n,e){return"@container style("+o(e.modifier)+": true)"},r)})}); 2 | //# sourceMappingURL=index.umd.js.map 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tailwindcss-signals", 3 | "version": "0.0.25", 4 | "description": "Grouped utility support for Tailwind CSS", 5 | "type": "module", 6 | "source": "index.ts", 7 | "exports": { 8 | "types": "./dist/types/index.d.ts", 9 | "require": "./dist/index.cjs", 10 | "default": "./dist/index.modern.js" 11 | }, 12 | "types": "dist/types/index.d.ts", 13 | "main": "./dist/index.cjs", 14 | "module": "./dist/index.module.js", 15 | "unpkg": "./dist/index.umd.js", 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "build": "microbundle", 19 | "dev": "microbundle watch" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/brandonmcconnell/tailwindcss-signals.git" 27 | }, 28 | "keywords": [ 29 | "css", 30 | "utility-classes", 31 | "group", 32 | "groups", 33 | "grouping", 34 | "signals", 35 | "plugin", 36 | "plugins", 37 | "tailwind", 38 | "tailwindcss" 39 | ], 40 | "author": "Brandon McConnell", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/brandonmcconnell/tailwindcss-signals/issues" 44 | }, 45 | "homepage": "https://github.com/brandonmcconnell/tailwindcss-signals#readme", 46 | "dependencies": { 47 | "@types/node": "^20.4.1" 48 | }, 49 | "devDependencies": { 50 | "@types/tailwindcss": "^3.1.0", 51 | "@typescript-eslint/eslint-plugin": "^5.59.8", 52 | "@typescript-eslint/parser": "^5.59.8", 53 | "eslint": "^8.41.0", 54 | "eslint-config-prettier": "^8.8.0", 55 | "eslint-plugin-prettier": "^5.0.0-alpha.1", 56 | "microbundle": "^0.15.1", 57 | "npm-run-all": "^4.1.5", 58 | "tailwindcss": "^3.4.3", 59 | "typescript": "^5.1.6" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /dist/index.modern.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.modern.js","sources":["../node_modules/tailwindcss/lib/util/createPlugin.js","../node_modules/tailwindcss/lib/public/create-plugin.js","../index.ts","../node_modules/tailwindcss/plugin.js"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nfunction createPlugin(plugin, config) {\n return {\n handler: plugin,\n config\n };\n}\ncreatePlugin.withOptions = function(pluginFunction, configFunction = ()=>({})) {\n const optionsFunction = function(options) {\n return {\n __options: options,\n handler: pluginFunction(options),\n config: configFunction(options)\n };\n };\n optionsFunction.__isOptionsFunction = true;\n // Expose plugin dependencies so that `object-hash` returns a different\n // value if anything here changes, to ensure a rebuild is triggered.\n optionsFunction.__pluginFunction = pluginFunction;\n optionsFunction.__configFunction = configFunction;\n return optionsFunction;\n};\nconst _default = createPlugin;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nconst _createPlugin = /*#__PURE__*/ _interop_require_default(require(\"../util/createPlugin\"));\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : {\n default: obj\n };\n}\nconst _default = _createPlugin.default;\n","import plugin from 'tailwindcss/plugin.js';\n\nconst getStyleVarName = (modifier: string | null) => `--tw-signal${modifier ? `_${modifier}` : ''}`;\n// using empty values here so the compiler plays nice and generates the styles without values\nconst EMPTY_VALUES = { values: { DEFAULT: '' } };\n\nexport default plugin(({ matchUtilities, matchVariant }) => {\n matchUtilities(\n {\n signal: (_, { modifier }) => {\n return {\n [getStyleVarName(modifier)]: 'true',\n };\n },\n },\n {\n ...EMPTY_VALUES,\n modifiers: 'any',\n }\n );\n\n matchVariant(\n 'signal',\n (_, { modifier }) => {\n return `@container style(${getStyleVarName(modifier)}: true)`;\n },\n EMPTY_VALUES\n );\n});\n","let createPlugin = require('./lib/public/create-plugin')\nmodule.exports = (createPlugin.__esModule ? createPlugin : { default: createPlugin }).default\n"],"names":["createPlugin","plugin","config","handler","Object","defineProperty","exports","value","enumerable","get","_default","withOptions","pluginFunction","configFunction","optionsFunction","options","__options","__isOptionsFunction","__pluginFunction","__configFunction","_interop_require_default","obj","__esModule","default","require$$0","getStyleVarName","modifier","EMPTY_VALUES","values","DEFAULT","index","matchUtilities","matchVariant","signal","_","_extends","modifiers"],"mappings":"2TAUA,SAASA,EAAaC,EAAQC,GAC1B,MAAO,CACHC,QAASF,EACTC,SAER,CAdAE,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQLV,EAAaW,YAAc,SAASC,EAAgBC,EAAiB,MAAA,CAAO,KACxE,MAAMC,EAAkB,SAASC,GAC7B,MAAO,CACHC,UAAWD,EACXZ,QAASS,EAAeG,GACxBb,OAAQW,EAAeE,GAEnC,EAMI,OALAD,EAAgBG,qBAAsB,EAGtCH,EAAgBI,iBAAmBN,EACnCE,EAAgBK,iBAAmBN,EAC5BC,CACX,EACA,MAAMJ,EAAWV,sBCpBjB,SAASoB,EAAyBC,GAC9B,OAAOA,GAAOA,EAAIC,WAAaD,EAAM,CACjCE,QAASF,EAEjB,CAdAjB,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQL,MAAMA,eAN8BU,EAAyBI,GAM9BD,UCd/B,MAAME,EAAmBC,kBAA0CA,EAAW,IAAIA,IAAa,IAEzFC,EAAe,CAAEC,OAAQ,CAAEC,QAAS,KAE1C,IAAAC,GAAe7B,GCLGD,EAAasB,WAAatB,EAAe,CAAEuB,QAASvB,IAAgBuB,SDKhE,EAAGQ,iBAAgBC,mBACvCD,EACE,CACEE,OAAQA,CAACC,GAAKR,eACL,CACL,CAACD,EAAgBC,IAAY,UAGlCS,KAEIR,EAAY,CACfS,UAAW,SAIfJ,EACE,SACA,CAACE,GAAKR,kCACuBD,EAAgBC,YAE7CC,EAEJ"} -------------------------------------------------------------------------------- /dist/index.cjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.cjs","sources":["../node_modules/tailwindcss/lib/util/createPlugin.js","../node_modules/tailwindcss/lib/public/create-plugin.js","../index.ts","../node_modules/tailwindcss/plugin.js"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nfunction createPlugin(plugin, config) {\n return {\n handler: plugin,\n config\n };\n}\ncreatePlugin.withOptions = function(pluginFunction, configFunction = ()=>({})) {\n const optionsFunction = function(options) {\n return {\n __options: options,\n handler: pluginFunction(options),\n config: configFunction(options)\n };\n };\n optionsFunction.__isOptionsFunction = true;\n // Expose plugin dependencies so that `object-hash` returns a different\n // value if anything here changes, to ensure a rebuild is triggered.\n optionsFunction.__pluginFunction = pluginFunction;\n optionsFunction.__configFunction = configFunction;\n return optionsFunction;\n};\nconst _default = createPlugin;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nconst _createPlugin = /*#__PURE__*/ _interop_require_default(require(\"../util/createPlugin\"));\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : {\n default: obj\n };\n}\nconst _default = _createPlugin.default;\n","import plugin from 'tailwindcss/plugin.js';\n\nconst getStyleVarName = (modifier: string | null) => `--tw-signal${modifier ? `_${modifier}` : ''}`;\n// using empty values here so the compiler plays nice and generates the styles without values\nconst EMPTY_VALUES = { values: { DEFAULT: '' } };\n\nexport default plugin(({ matchUtilities, matchVariant }) => {\n matchUtilities(\n {\n signal: (_, { modifier }) => {\n return {\n [getStyleVarName(modifier)]: 'true',\n };\n },\n },\n {\n ...EMPTY_VALUES,\n modifiers: 'any',\n }\n );\n\n matchVariant(\n 'signal',\n (_, { modifier }) => {\n return `@container style(${getStyleVarName(modifier)}: true)`;\n },\n EMPTY_VALUES\n );\n});\n","let createPlugin = require('./lib/public/create-plugin')\nmodule.exports = (createPlugin.__esModule ? createPlugin : { default: createPlugin }).default\n"],"names":["createPlugin","plugin","config","handler","Object","defineProperty","exports","value","enumerable","get","_default","withOptions","pluginFunction","configFunction","optionsFunction","options","__options","__isOptionsFunction","__pluginFunction","__configFunction","_interop_require_default","obj","__esModule","default","require$$0","getStyleVarName","modifier","EMPTY_VALUES","values","DEFAULT","_ref","matchVariant","matchUtilities","signal","_","_ref2","_ref3","_extends","modifiers","_ref4"],"mappings":"2TAUA,SAASA,EAAaC,EAAQC,GAC1B,MAAO,CACHC,QAASF,EACTC,SAER,CAdAE,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQLV,EAAaW,YAAc,SAASC,EAAgBC,EAAiB,MAAA,CAAO,KACxE,MAAMC,EAAkB,SAASC,GAC7B,MAAO,CACHC,UAAWD,EACXZ,QAASS,EAAeG,GACxBb,OAAQW,EAAeE,GAEnC,EAMI,OALAD,EAAgBG,qBAAsB,EAGtCH,EAAgBI,iBAAmBN,EACnCE,EAAgBK,iBAAmBN,EAC5BC,CACX,EACA,MAAMJ,EAAWV,sBCpBjB,SAASoB,EAAyBC,GAC9B,OAAOA,GAAOA,EAAIC,WAAaD,EAAM,CACjCE,QAASF,EAEjB,CAdAjB,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQL,MAAMA,eAN8BU,EAAyBI,GAM9BD,UCdzBE,EAAkB,SAACC,GAAuB,MAAA,eAAmBA,EAAQ,IAAOA,EAAa,GAAE,EAE3FC,EAAe,CAAEC,OAAQ,CAAEC,QAAS,KAE3B5B,GAAAA,GCLGD,EAAasB,WAAatB,EAAe,CAAEuB,QAASvB,IAAgBuB,SDKhE,SAAAO,GAAqC,IAAlBC,EAAYD,EAAZC,cACvCC,EADqCF,EAAdE,gBAErB,CACEC,OAAQ,SAACC,EAACC,GAAkBC,IAAAA,EAC1B,OAAAA,MACGX,EAFiBU,EAART,WAEmB,OAAMU,CAEvC,GACDC,EAEIV,CAAAA,EAAAA,GACHW,UAAW,SAIfP,EACE,SACA,SAACG,EAACK,GACA,MAA2Bd,oBAAAA,EADfc,EAARb,UACgD,SACtD,EACAC,EAEJ"} -------------------------------------------------------------------------------- /dist/index.umd.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.umd.js","sources":["../node_modules/tailwindcss/lib/util/createPlugin.js","../node_modules/tailwindcss/lib/public/create-plugin.js","../index.ts","../node_modules/tailwindcss/plugin.js"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nfunction createPlugin(plugin, config) {\n return {\n handler: plugin,\n config\n };\n}\ncreatePlugin.withOptions = function(pluginFunction, configFunction = ()=>({})) {\n const optionsFunction = function(options) {\n return {\n __options: options,\n handler: pluginFunction(options),\n config: configFunction(options)\n };\n };\n optionsFunction.__isOptionsFunction = true;\n // Expose plugin dependencies so that `object-hash` returns a different\n // value if anything here changes, to ensure a rebuild is triggered.\n optionsFunction.__pluginFunction = pluginFunction;\n optionsFunction.__configFunction = configFunction;\n return optionsFunction;\n};\nconst _default = createPlugin;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nconst _createPlugin = /*#__PURE__*/ _interop_require_default(require(\"../util/createPlugin\"));\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : {\n default: obj\n };\n}\nconst _default = _createPlugin.default;\n","import plugin from 'tailwindcss/plugin.js';\n\nconst getStyleVarName = (modifier: string | null) => `--tw-signal${modifier ? `_${modifier}` : ''}`;\n// using empty values here so the compiler plays nice and generates the styles without values\nconst EMPTY_VALUES = { values: { DEFAULT: '' } };\n\nexport default plugin(({ matchUtilities, matchVariant }) => {\n matchUtilities(\n {\n signal: (_, { modifier }) => {\n return {\n [getStyleVarName(modifier)]: 'true',\n };\n },\n },\n {\n ...EMPTY_VALUES,\n modifiers: 'any',\n }\n );\n\n matchVariant(\n 'signal',\n (_, { modifier }) => {\n return `@container style(${getStyleVarName(modifier)}: true)`;\n },\n EMPTY_VALUES\n );\n});\n","let createPlugin = require('./lib/public/create-plugin')\nmodule.exports = (createPlugin.__esModule ? createPlugin : { default: createPlugin }).default\n"],"names":["createPlugin","plugin","config","handler","Object","defineProperty","exports","value","enumerable","get","_default","withOptions","pluginFunction","configFunction","optionsFunction","options","__options","__isOptionsFunction","__pluginFunction","__configFunction","_interop_require_default","obj","__esModule","default","require$$0","getStyleVarName","modifier","EMPTY_VALUES","values","DEFAULT","_ref","matchVariant","matchUtilities","signal","_","_ref2","_ref3","_extends","modifiers","_ref4"],"mappings":"giBAUA,SAASA,EAAaC,EAAQC,GAC1B,MAAO,CACHC,QAASF,EACTC,SAER,CAdAE,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQLV,EAAaW,YAAc,SAASC,EAAgBC,EAAiB,MAAA,CAAO,KACxE,MAAMC,EAAkB,SAASC,GAC7B,MAAO,CACHC,UAAWD,EACXZ,QAASS,EAAeG,GACxBb,OAAQW,EAAeE,GAEnC,EAMI,OALAD,EAAgBG,qBAAsB,EAGtCH,EAAgBI,iBAAmBN,EACnCE,EAAgBK,iBAAmBN,EAC5BC,CACX,EACA,MAAMJ,EAAWV,sBCpBjB,SAASoB,EAAyBC,GAC9B,OAAOA,GAAOA,EAAIC,WAAaD,EAAM,CACjCE,QAASF,EAEjB,CAdAjB,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQL,MAAMA,eAN8BU,EAAyBI,GAM9BD,UCdzBE,EAAkB,SAACC,GAAuB,MAAA,eAAmBA,EAAQ,IAAOA,EAAa,GAAE,EAE3FC,EAAe,CAAEC,OAAQ,CAAEC,QAAS,YAE3B5B,GCLGD,EAAasB,WAAatB,EAAe,CAAEuB,QAASvB,IAAgBuB,SDKhE,SAAAO,GAAqC,IAAlBC,EAAYD,EAAZC,cACvCC,EADqCF,EAAdE,gBAErB,CACEC,OAAQ,SAACC,EAACC,GAAkBC,IAAAA,EAC1B,OAAAA,MACGX,EAFiBU,EAART,WAEmB,OAAMU,CAEvC,GACDC,EAEIV,CAAAA,EAAAA,GACHW,UAAW,SAIfP,EACE,SACA,SAACG,EAACK,GACA,MAA2Bd,oBAAAA,EADfc,EAARb,UACgD,SACtD,EACAC,EAEJ"} -------------------------------------------------------------------------------- /dist/index.module.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.module.js","sources":["../node_modules/tailwindcss/lib/util/createPlugin.js","../node_modules/tailwindcss/lib/public/create-plugin.js","../index.ts","../node_modules/tailwindcss/plugin.js"],"sourcesContent":["\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nfunction createPlugin(plugin, config) {\n return {\n handler: plugin,\n config\n };\n}\ncreatePlugin.withOptions = function(pluginFunction, configFunction = ()=>({})) {\n const optionsFunction = function(options) {\n return {\n __options: options,\n handler: pluginFunction(options),\n config: configFunction(options)\n };\n };\n optionsFunction.__isOptionsFunction = true;\n // Expose plugin dependencies so that `object-hash` returns a different\n // value if anything here changes, to ensure a rebuild is triggered.\n optionsFunction.__pluginFunction = pluginFunction;\n optionsFunction.__configFunction = configFunction;\n return optionsFunction;\n};\nconst _default = createPlugin;\n","\"use strict\";\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"default\", {\n enumerable: true,\n get: function() {\n return _default;\n }\n});\nconst _createPlugin = /*#__PURE__*/ _interop_require_default(require(\"../util/createPlugin\"));\nfunction _interop_require_default(obj) {\n return obj && obj.__esModule ? obj : {\n default: obj\n };\n}\nconst _default = _createPlugin.default;\n","import plugin from 'tailwindcss/plugin.js';\n\nconst getStyleVarName = (modifier: string | null) => `--tw-signal${modifier ? `_${modifier}` : ''}`;\n// using empty values here so the compiler plays nice and generates the styles without values\nconst EMPTY_VALUES = { values: { DEFAULT: '' } };\n\nexport default plugin(({ matchUtilities, matchVariant }) => {\n matchUtilities(\n {\n signal: (_, { modifier }) => {\n return {\n [getStyleVarName(modifier)]: 'true',\n };\n },\n },\n {\n ...EMPTY_VALUES,\n modifiers: 'any',\n }\n );\n\n matchVariant(\n 'signal',\n (_, { modifier }) => {\n return `@container style(${getStyleVarName(modifier)}: true)`;\n },\n EMPTY_VALUES\n );\n});\n","let createPlugin = require('./lib/public/create-plugin')\nmodule.exports = (createPlugin.__esModule ? createPlugin : { default: createPlugin }).default\n"],"names":["createPlugin","plugin","config","handler","Object","defineProperty","exports","value","enumerable","get","_default","withOptions","pluginFunction","configFunction","optionsFunction","options","__options","__isOptionsFunction","__pluginFunction","__configFunction","_interop_require_default","obj","__esModule","default","require$$0","getStyleVarName","modifier","EMPTY_VALUES","values","DEFAULT","_ref","matchVariant","matchUtilities","signal","_","_ref2","_ref3","_extends","modifiers","_ref4"],"mappings":"2TAUA,SAASA,EAAaC,EAAQC,GAC1B,MAAO,CACHC,QAASF,EACTC,SAER,CAdAE,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQLV,EAAaW,YAAc,SAASC,EAAgBC,EAAiB,MAAA,CAAO,KACxE,MAAMC,EAAkB,SAASC,GAC7B,MAAO,CACHC,UAAWD,EACXZ,QAASS,EAAeG,GACxBb,OAAQW,EAAeE,GAEnC,EAMI,OALAD,EAAgBG,qBAAsB,EAGtCH,EAAgBI,iBAAmBN,EACnCE,EAAgBK,iBAAmBN,EAC5BC,CACX,EACA,MAAMJ,EAAWV,sBCpBjB,SAASoB,EAAyBC,GAC9B,OAAOA,GAAOA,EAAIC,WAAaD,EAAM,CACjCE,QAASF,EAEjB,CAdAjB,OAAOC,eAAwBC,EAAA,aAAc,CACzCC,OAAO,IAEXH,OAAOC,eAAeC,EAAS,UAAW,CACtCE,YAAY,EACZC,IAAK,WACD,OAAOC,CACV,IAQL,MAAMA,eAN8BU,EAAyBI,GAM9BD,UCdzBE,EAAkB,SAACC,GAAuB,MAAA,eAAmBA,EAAQ,IAAOA,EAAa,GAAE,EAE3FC,EAAe,CAAEC,OAAQ,CAAEC,QAAS,KAE3B5B,GAAAA,GCLGD,EAAasB,WAAatB,EAAe,CAAEuB,QAASvB,IAAgBuB,SDKhE,SAAAO,GAAqC,IAAlBC,EAAYD,EAAZC,cACvCC,EADqCF,EAAdE,gBAErB,CACEC,OAAQ,SAACC,EAACC,GAAkBC,IAAAA,EAC1B,OAAAA,MACGX,EAFiBU,EAART,WAEmB,OAAMU,CAEvC,GACDC,EAEIV,CAAAA,EAAAA,GACHW,UAAW,SAIfP,EACE,SACA,SAACG,EAACK,GACA,MAA2Bd,oBAAAA,EADfc,EAARb,UACgD,SACtD,EACAC,EAEJ"} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Signals for Tailwind CSS

2 | 3 |
4 | 5 | [![minified size](https://img.shields.io/bundlephobia/min/tailwindcss-signals)](https://bundlephobia.com/package/tailwindcss-signals) 6 | [![license](https://img.shields.io/github/license/brandonmcconnell/tailwindcss-signals?label=license)](https://github.com/brandonmcconnell/tailwindcss-signals/blob/main/LICENSE) 7 | [![version](https://img.shields.io/npm/v/tailwindcss-signals)](https://www.npmjs.com/package/tailwindcss-signals) 8 | [![twitter](https://img.shields.io/twitter/follow/branmcconnell)](https://twitter.com/branmcconnell) 9 | 10 |
11 | 12 |
13 | 14 | ### ⚠️ This plugin is experimental and relies on [style queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_size_and_style_queries#container_style_queries_2) (via container queries), which are not yet widely supported in browsers. 15 | 16 | The good news is that Safari and Firefox, the browsers lacking support, have already begun implementing style queries in their development versions, so it's only a matter of time before they're widely available. 17 | 18 | See the browser compatibility table on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_size_and_style_queries#browser_compatibility) or [caniuse](https://caniuse.com/css-container-queries-style) for more information. 19 | 20 |
21 | 22 | Signals for Tailwind CSS is a plugin that utilizes [style queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_size_and_style_queries#container_style_queries_2) (via container queries) to reactively enable a custom state, which can then be consumed by any of its descendants in the DOM. 23 | 24 | `signal` is similar to the existing `group` variant/utility in that both provide methods for styling elements based on their ancestors' state. Unlike `group` states, however, signal states can be explicitly signaled, allowing their state to be both set and consumed with a single, simple, unchained variant. 25 | 26 | This reduces development effort and the need to compose a chain of variants, improving the developer experience with a more declarative API. 27 | 28 | Depending on your use case, a traditional `group` may make more sense, but often, particularly when managing a parent or ancestor state with anything more complex than a simple `peer-X` or `group-X`, a `signal` may be a simpler option. 29 | 30 | ## Installation 31 | 32 | You can install the plugin via npm: 33 | 34 | ```bash 35 | npm install tailwindcss-signals 36 | ``` 37 | 38 | Then, include it in your `tailwind.config.js`: 39 | 40 | ```js 41 | module.exports = { 42 | plugins: [ 43 | require('tailwindcss-signals'), 44 | ] 45 | } 46 | ``` 47 | 48 | ## Usage 49 | 50 | The plugin introduces the `signal` variant, which can be used to apply styles based on an ancestor's signaled state. 51 | 52 | Here's an example comparing the traditional approach with the new signals approach: 53 | 54 |
55 | 56 | #### Example: Without Signals 57 | ```html 58 | 👈🏼 check/uncheck here 59 |
60 |
or hover here
61 |
62 | ``` 63 | Open this example in Tailwind Play: https://play.tailwindcss.com/E3ig9SPTsc 64 | 65 |
66 | 67 | #### Example: With Signals 68 | ```html 69 | 👈🏼 check/uncheck here 70 |
71 |
or hover here
72 |
73 | ``` 74 | Open this example in Tailwind Play: https://play.tailwindcss.com/weFkMf4U5K 75 | 76 |
77 | 78 | Notice how, with signals, we don't have to use any arbitrary selector variants like `[&>div]` and can instead apply those styles directly to the targeted descendants. This allows us to consolidate some redundancy in the parent so that whatever condition activates the signal only needs to be specified once rather than once per style/utility. 79 | 80 | The benefits of Signals for Tailwind CSS become more apparent as the complexity of your styles and conditions increase. 81 | 82 | ### Activating a `signal` based on a descendant condition 83 | 84 | The general purpose of this plugin is to provide a declarative approach to applying styles based on an _**ancestor's**_ state. 85 | 86 | However, thanks to the power of the [`:has()`](https://developer.mozilla.org/en-US/docs/Web/CSS/:has) CSS pseudo-class, we can even activate a signal based on a _**descendant's**_ state. 87 | 88 |
89 | 90 | #### Example: Descendant condition 91 | ```html 92 |
93 | 👈🏼 check/uncheck here 94 |
or hover here
95 |
96 | ``` 97 | Open this example in Tailwind Play: https://play.tailwindcss.com/YnlzSITNqF 98 | 99 |
100 | 101 | This is most useful for situations where you want to apply styles to an entire block based on the current state of one of its descendants. 102 | 103 | Here are a few examples of cases where such a feature might be useful: 104 | * Activating a signal based on the presence or visibility of a specific child element 105 | * Activating a signal on a form based on the validity of one or more of its descendant form fields 106 | * Activating a signal when a specific descendant element is focused or hovered 107 | * Activating a signal based on the presence of a specific class on a descendant element 108 | * and many more! 109 | 110 | ⚠️ Some cautions: 111 | * Watch out for circularity issues. If you set up a signal that activates based on a descendant's state, and that descendant's state is also based on the signal, you may run into issues. 112 | * In some cases, if you want to check if **any** descendant is focused, for example, you may not need `:has()` and could use a simpler pseudo-class variant such as… 113 | * `focus-within:signal` instead of `has-[:focus]:signal` 114 | * `valid:signal` instead of `has-[:valid]:signal` (for a `form`, which checks if all form contents are valid) 115 | * This is a bit less declarative when you use `:has()`, but for use cases where you would need it, it would likely still be simpler than the alternative. 116 | 117 | ### Differentiating signals 118 | 119 | When using multiple signals, you may run into situations where you want one signal nested in another, which could cause issues. In that case, you can distinguish signals apart by naming them using the modifier syntax built into Tailwind CSS, the same naming convention used for `group` and `peer` variants. 120 | 121 | 122 |
123 | 124 | #### Example: Naming a signal 125 | ```html 126 | 👈🏼 check/uncheck here 127 |
✨ hover/unhover here ✨
128 |
129 |
press me
135 |
136 | ``` 137 | Open this example in Tailwind Play: https://play.tailwindcss.com/MkWvEuaWtO 138 | 139 |
140 | 141 | By giving a signal a name, you can ensure it is unique and doesn't conflict with other signals. You can name a signal by adding a slash and the name after the `signal` variant, like `signal/{name}`. 142 | 143 | Consuming a named signal is the same as consuming a regular signal, but with the name appended to the variant: `signal/{name}`. 144 | 145 | For more information on this modifier syntax, see [Differentiating peers](https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-peers) from the official Tailwind CS documentation. 146 | 147 | ## Why use Signals for Tailwind CSS? 148 | 149 | Signals for Tailwind CSS provides a more declarative and straightforward approach to applying styles based on an ancestor's state. Leveraging style queries (via container queries) eliminates the need for complex selector chaining and arbitrary targeting, resulting in a cleaner and more maintainable codebase. 150 | 151 | This plugin is particularly useful for: 152 | 153 | - Simplifying the application of styles based on ancestor states 154 | - Improving developer experience with a more declarative API 155 | - Reducing the need for complex selector chaining and arbitrary targeting 156 | 157 | ## Why NOT use Signals for Tailwind CSS? 158 | 159 | **⚠️ Browser support for [style queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_size_and_style_queries#container_style_queries_2) is still limited, so Signals for Tailwind CSS may not be suitable for projects that require broad compatibility.** 160 | 161 | The good news is that Safari and Firefox, the browsers lacking support, have already begun implementing style queries in their development versions, so it's only a matter of time before they're widely available. 162 | 163 | See the browser compatibility table on [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_size_and_style_queries#browser_compatibility) or [caniuse](https://caniuse.com/css-container-queries-style) for more information. 164 | 165 | 166 | --- 167 | 168 | I hope you find `tailwindcss-signals` a valuable addition to your projects. If you have any issues or suggestions, don't hesitate to open an issue or pull request. 169 | 170 | If you liked this, you might also like my other Tailwind CSS plugins: 171 | * [tailwindcss-multi](https://github.com/brandonmcconnell/tailwindcss-multi): Group utilities together by variant 172 | * [tailwindcss-mixins](https://github.com/brandonmcconnell/tailwindcss-mixins): Construct reusable & aliased sets of utilities inline 173 | * [tailwindcss-members](https://github.com/brandonmcconnell/tailwindcss-members): Apply styles based on child or descendant state, the inverse of groups 174 | * [tailwindcss-selector-patterns](https://github.com/brandonmcconnell/tailwindcss-selector-patterns): Dynamic CSS selector patterns 175 | * [tailwindcss-js](https://github.com/brandonmcconnell/tailwindcss-js): Effortless build-time JS script injection 176 | * [tailwindcss-directional-shadows](https://github.com/brandonmcconnell/tailwindcss-directional-shadows): Supercharge your shadow utilities with added directional support (includes directional `shadow-border` utilities too ✨) 177 | * [tailwindcss-default-shades](https://github.com/brandonmcconnell/tailwindcss-default-shades): Default shades for simpler color utility classes 178 | * [tailwind-lerp-colors](https://github.com/brandonmcconnell/tailwind-lerp-colors): Expand your color horizons and take the fuss out of generating new—or expanding existing—color palettes --------------------------------------------------------------------------------