{"id":17930209,"url":"https://github.com/samwillis/x-style","last_synced_at":"2025-06-27T04:32:33.993Z","repository":{"id":149984041,"uuid":"622267183","full_name":"samwillis/x-style","owner":"samwillis","description":"x-style (Extract Style) - A Tiny Inline Nested CSS Library","archived":false,"fork":false,"pushed_at":"2025-04-27T13:28:44.000Z","size":677,"stargazers_count":45,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-19T23:12:15.976Z","etag":null,"topics":["css","nested-css","style","tailwind","tailwindcss"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samwillis.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"license.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2023-04-01T15:56:09.000Z","updated_at":"2025-04-27T13:28:48.000Z","dependencies_parsed_at":"2024-01-19T01:10:07.561Z","dependency_job_id":"1cc9c9e0-164f-4747-b801-a42fa3a98059","html_url":"https://github.com/samwillis/x-style","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/samwillis/x-style","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samwillis%2Fx-style","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samwillis%2Fx-style/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samwillis%2Fx-style/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samwillis%2Fx-style/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samwillis","download_url":"https://codeload.github.com/samwillis/x-style/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samwillis%2Fx-style/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262188396,"owners_count":23272341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","nested-css","style","tailwind","tailwindcss"],"created_at":"2024-10-28T21:12:26.086Z","updated_at":"2025-06-27T04:32:33.982Z","avatar_url":"https://github.com/samwillis.png","language":"HTML","funding_links":[],"categories":["HTML"],"sub_categories":[],"readme":"# x-style (Extract Style) - A Tiny Inline Nested CSS Library\n\nx-style is a tiny library that adds full css support to inline styles, allowing you to\ndefine style where they are being used with the new nested syntax. It does this by extracting\ninline stiles from your html on the fly:\n\n```html\n\u003cdiv\n  x-style=\"\n  color: red;\n  font-size: 30px;\n  \u0026:hover {\n    background-color: yellow;\n  }\n  \u0026 \u003e span {\n    color: green;\n    \u0026::after {\n      display: inline;\n      content: '!';\n    }\n  }\n\"\n\u003e\n  Hello \u003cspan\u003eWorld\u003c/span\u003e\n\u003c/div\u003e\n```\n\nSee the [demo](https://samwillis.uk/x-style/) for more examples.\n\nThe styles are only parsed once so *repeated use of the same style is very fast*, the\nlibrary adds an attribute to each element for use by a css selector to find the\nstyles and apply them. Repeated use of the same `x-style` value uses the sate generated\nstylesheet. If you add additional elements or change styles after the page \nloads the library will automatically find and update the styles.\n\nx-style is tiny, as little as 1.8kb with the un-nest plugin or 856 bytes without,\nand designed to be pasted directly into your html, this ensures that it runs \nsynchronously, as your pages loads, with no FOUC (flash of unstyled content).\n\nIn the newest browsers - Chrome 112+, Safari 16.5+ and Firefox 117+ - the nested styles work\nwithout any extra code, but for \"older\" browsers you need to use the `x-style-unnest.js`\nplugin. See https://caniuse.com/css-nesting.\n\nThere is a [small benchmark](https://samwillis.uk/x-style/benchmark/run.html) that shows \nfor a typical tailwind page with about 400 style classes the overhead of x-style is about \n5ms on load.\n\n## Why?\n\nUtility class toolkits like Tailwind have shown that defining styles inline can be\nbetter than using a css file. It encapsulated the style with the component, making it \neasier to understand and maintain, this is known as \n[\"locality of behaviour\"](https://htmx.org/essays/locality-of-behaviour/). \nWhen used in combination with a component based framework it results in a very clean \nand maintainable code.\n\nHowever, increasingly these frameworks are implementing more and more css as classes, \ncreating a domain specific language shoehorned into the css class system that is not \neasy to understand. The aim of x-style is to allow this locality of behaviour, but by \nusing native css syntax. Additionally these toolkits require separate tooling or build \nsteps, neither of which are needed with x-style.\n\nx-style is the ideal companion to tools such as [htmx](http://htmx.org) and \n[Alpine.js](http://alpinejs.dev).\n\n## How to use\n\nThe recommended minimal setup is to add this to your `\u003chead\u003e` directly before `\u003c/head\u003e`:\n\n```html\n\u003cscript\u003e\n// x-style core:\n(()=\u003e{var r=document,f=(e,t)=\u003ee.querySelectorAll(t),h=[],b=[],e=(a,i)=\u003e{var e,\no=\"\",s=0,n=new Map,l=a+\"-match\",t=new MutationObserver(e=\u003e{for(var t of e)if(\n\"attributes\"===t.type)d(t.target);else if(\"childList\"===t.type)for(\nvar r of t.addedNodes)r instanceof HTMLElement\u0026\u0026(r.hasAttribute(a)\u0026\u0026d(r),[...f(r\n,`[${a}]`)].forEach(d));u()}),u=()=\u003e{o\u0026\u0026((e=r.createElement(\"style\")\n).innerHTML=o,r.head.appendChild(e),e=null,o=\"\")},c=(e,t)=\u003e{var t=l+\"-\"+n.get(t)\n,r=\"__\"+l;return e[r]\u0026\u0026e.removeAttribute(e[r]),e.setAttribute(t,\"\"),e[r]=t},\nd=e=\u003e{var t,r=e.getAttribute(a);!r||n.has(r)?i||c(e,r):(n.set(r,++s),\nt=i?`[${a}=\"${CSS.escape(r)}\"]`:`[${c(e,r)}]`,h.forEach(e=\u003er=e(r)),\nt+=` { ${r} }`,b.forEach(e=\u003et=e(t)),o+=t+\"\\n\")};f(r,`[${a}]`).forEach(d),u(),\nt.observe(r.documentElement,{attributes:!0,attributeFilter:[a],childList:!0,\nsubtree:!0})};e.pre=h,e.post=b,e.version=\"0.0.3\",window.xstyle=e})();\n// x-style unnest plugin:\n(()=\u003e{var c=CSS.supports(\"selector(\u0026)\"),r=window.xstyle,e=r=\u003e{if(c)return r;\nr=r.replace(/\\/\\*[^*]*\\*+([^/][^*]*\\*+)*\\//g,\"\");for(var e,t,s,a=\"\",p=\"\",h=0,\ni=\"\",l=r.length,n=[],o=[],u=()=\u003e{i.match(/\\S/)\u0026\u0026(p+=n[n.length-1]+\"{\"+i+\"}\",i=\"\"\n)};h\u003cl;)\"\\\\\"===(s=r[h])?(a+=r.slice(h,h+2),h+=2):(\";\"===s?(i+=a+s,a=\"\"\n):\"}\"===s?(i+=a,a=\"\",u(),o.pop()?p+=s:n.pop()):\"{\"===s?(u(),e=n[n.length-1],(t=a\n).match(/^\\s*@/)?(o.push(!0),p+=t+s):(e\u0026\u0026(t=((t,r)=\u003e{var e,s=r.match(/^\\s*/)[0],\na=r.match(/\\s*$/)[0],p=0;for(t=t.trim(),r=r.trim();p\u003ct.length;)if(\"\\\\\"===(e=t[p]\n))p+=2;else if('\"'===e||\"'\"===e){var h=e;for(p++;e!==h;)p+=\"\\\\\"===(e=t[p])?2:1;\np++}else if(\"(\"===e)for(var i=1;\")\"!==e\u0026\u00260\u003ci;)\"(\"===(e=t[p])\u0026\u0026i++,\")\"===e\u0026\u0026i--,\np++;else{if(\",\"===e){t=\":is(\"+t+\")\";break}p++}return s+[...r.match(\n/(\\\\.|[^,])+/g)].map(r=\u003e{var e=(r=r.trim()).replace(/(^|[^\\\\])(\u0026)/g,\"$1\"+t);\nreturn e=e===r?t+\" \"+r:e}).join(\", \")+a})(e,t)),n.push(t),o.push(!1)),a=\"\"):a+=s\n,h++);return p};r.post.push(e),r.unnest=e})();\n// Activate x-style with the \"x-style\" attribute:\nxstyle(\"x-style\");\n\u003c/script\u003e\n```\n\nThis is both the core x-style code and the unnest plugin. The `xstyle(\"x-style\")`\nactivates the library, it will find all elements with the `x-style` attribute and\nprocess their styles.\n\nAlternatively you can link to the x-style code and plugins directly, however as they are\ntiny it is better, and faster, to inline them.\n\nThere is an optional second boolean parameter that disables adding a `x-style-match` \nattribute to the elements that have had their styles applied. Instead the naked \n`x-style` attribute value if used for the selector, but this can be quite slow. As the \ndefault behaviour mutates the DOM it may cause problems with some frameworks, if so set \nthis to `true` to disable the behaviour.\n\n```js\nxstyle(\"x-style\", true);\n```\n\nSee below for details on activating the `envvar` and `@apply` plugins.\n\nFinally, you can also load it from a cdn:\n\n```html\n\u003cscript src=\"https://www.unpkg.com/@sgwillis/x-style@0.0.1/dist/x-style-all.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\nxstyle(\"x-style\");\n\u003c/script\u003e\n```\n\n## Plugins:\n\nThere are a number of tiny plugins that add extra functionality to x-style. At a minimum\nyou probably want to use the `x-style-unnest.js` plugin to add support for \"old\"\nbrowsers - well most browsers - that don't support nested styles.\n\nPlugins are loaded by adding the plugins code to the page `\u003chead\u003e` directly after the\nx-style code.\n\n### x-style-unnest.js\n\nThe unnest plugin adds support for old browsers that don't support nested styles. It \ndoes this by parsing the style and unnesting it, so the above example would be \nconverted to:\n\n```css\ndiv[x-style-match-1] {\n  color: red;\n  font-size: 30px;\n}\ndiv[x-style-match-1]:hover {\n  background-color: yellow;\n}\ndiv[x-style-match-1] \u003e span {\n  color: green;\n}\n```\n\nIt also supports the `@media`, `@supports` and other `@` rules, correctly promoting\nthe \"at\" rules to the top level.\n\nThe implementation is not perfect, but it works well enough for most cases. This is\nintentional so as to keep the code small and fast. An alternative would be to used\na browser build of postcss, but that would be about 100kb (100x the size of x-style) \nand async, resulting in a flash of unstyled content.\n\n### x-style-envvar.js\n\nThe envvar plugin adds support for custom \n[environment variables](https://developer.mozilla.org/en-US/docs/Web/CSS/env)\nin styles. As it's not possible to use `var()` in media queries (custom properties are \npart of the cascade and require a DOM node which media queries don't have), this plugin \nadds support for defining custom environment variables which are then substituted in \nyour css. Custom environment variables are planned for css, but there is not yet any \nstandard for defining them.\n\nYou define custom environment variables for x-style via js like this:\n\n```js\nxstyle.env = {\n  \"--my-break-point\": \"800px\",\n};\n```\n\nYou can then use the custom environment variables in your css like this:\n\n```css\n@media (min-width: env(--my-break-point)) {\n  /* ... */\n}\n```\n\nNote that this implementation of environment variables does not update the css when the\nvalue changes.\n\nTo use the plugin, add the following to the x-style `\u003cscript\u003e` in the `\u003chead\u003e`\nimmediately after the x-style code but before the `xstyle(\"x-style\")` call:\n\n```html\n(()=\u003e{var r=window.xstyle;r.env=r.env||{},r.pre.push(e=\u003e{for(var n,t,i,l=r.env,\np=[...e.split(/\\senv\\((--[^),]+)/g)];1\u003cp.length;)n=p.pop(),t=p.pop().trim(),\n\")\"===n[0]?n=n.slice(1):\",\"===n[0]\u0026\u0026([i,n]=n.split(\")\",2)),void 0!==l[t]?p[\np.length-1]+=\" \"+l[t]+n:p[p.length-1]+=i?\" \"+i.slice(1).trim()+n:\" \"+n;return p[\n0]})})();\n```\n\n### x-style-apply.js\n\nThe apply plugin implements `@apply`, extracting the css rules from the class defined in\nthe current document and inserting then into the new css.\n\nSo, if you have a class defined like this:\n\n```css\n.my-class {\n  color: red;\n}\n```\n\nYou can then use it like this:\n\n```html\n\u003cdiv\n  x-style=\"\n  \u0026:hover {\n    @apply my-class;\n  }\n\"\n\u003e\n  Hello\n\u003c/div\u003e\n```\n\nThis is not a official standard, and is unlikely to be, but it's a very useful feature. \nHowever it is recommended that you use `var()` instead as it's a standard.\n\nTo use the plugin, add the following to the xstyle `\u003cscript\u003e` in the `\u003chead\u003e`\nimmediately after the xstyle code but before the `xstyle(\"x-style\")` call:\n\n```html\n(()=\u003e{var s=/@apply ([^;}$]*);?/g,n={},t=e=\u003e{if(\".\"!==e[0]\u0026\u0026(e=\".\"+e),n[e]\n)return n[e];var s=[];for(const t of document.styleSheets)for(\nconst r of t.cssRules)r.selectorText===e\u0026\u0026s.push(r);return s.map(e=\u003e{e=e.cssText\nreturn e.slice(e.indexOf(\"{\")+1,e.lastIndexOf(\"}\"))}).join(\" \")};\nxstyle.pre.push(e=\u003ee.replace(s,(e,s)=\u003es.split(\" \").map(t).join(\" \")))})();\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamwillis%2Fx-style","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamwillis%2Fx-style","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamwillis%2Fx-style/lists"}