{"id":13514406,"url":"https://github.com/gnat/css-scope-inline","last_synced_at":"2025-05-15T09:06:02.552Z","repository":{"id":192840896,"uuid":"687429394","full_name":"gnat/css-scope-inline","owner":"gnat","description":"🌘 Scope your inline style tags in pure vanilla CSS! Only 16 lines. No build. No dependencies.","archived":false,"fork":false,"pushed_at":"2024-11-30T13:49:03.000Z","size":182,"stargazers_count":626,"open_issues_count":2,"forks_count":14,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-05-15T09:06:01.497Z","etag":null,"topics":["css","dom","frontend","html","htmx","hyperscript","inline","inline-css","inline-style","inline-styles","lob","locality-of-behavior","scoped-css","stylesheet","surreal","tailwind","tailwind-alternative","tailwindcss"],"latest_commit_sha":null,"homepage":"https://gnat.github.io/css-scope-inline/example.html","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/gnat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-05T10:45:35.000Z","updated_at":"2025-05-14T18:52:50.000Z","dependencies_parsed_at":"2023-10-04T07:50:39.638Z","dependency_job_id":"19010184-cb21-497e-9386-777f420687a9","html_url":"https://github.com/gnat/css-scope-inline","commit_stats":{"total_commits":161,"total_committers":3,"mean_commits":"53.666666666666664","dds":"0.012422360248447228","last_synced_commit":"b75583fe2bc4af41c4f187a5bb7ef5a790ce90cd"},"previous_names":["gnat/css-scope-inline"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnat%2Fcss-scope-inline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnat%2Fcss-scope-inline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnat%2Fcss-scope-inline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gnat%2Fcss-scope-inline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gnat","download_url":"https://codeload.github.com/gnat/css-scope-inline/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254310513,"owners_count":22049468,"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","dom","frontend","html","htmx","hyperscript","inline","inline-css","inline-style","inline-styles","lob","locality-of-behavior","scoped-css","stylesheet","surreal","tailwind","tailwind-alternative","tailwindcss"],"created_at":"2024-08-01T05:00:55.569Z","updated_at":"2025-05-15T09:06:02.531Z","avatar_url":"https://github.com/gnat.png","language":"HTML","readme":"# 🌘 CSS Scope Inline\n\n![cover](https://github.com/gnat/css-scope-inline/assets/24665/c4935c1b-34e3-4220-9d42-11f064999a57)\n(Art by [shahabalizadeh](https://www.artstation.com/artwork/zDgdd))\n\n## Why does this exist?\n\n* You want an easy inline vanilla CSS experience without Tailwind CSS.\n* Hate creating unique class names over.. and over.. to use once.\n* You want to co-locate your styles for ⚡️ [Locality of Behavior (LoB)](https://htmx.org/essays/locality-of-behaviour/)\n* You wish `this` would work in `\u003cstyle\u003e` tags.\n* Want all CSS features: [Nesting](https://caniuse.com/css-nesting), animations. Get scoped [`@keyframes`](https://github.com/gnat/css-scope-inline/blob/main/example.html#L50)!\n* You wish `@media` queries were shorter for [responsive design](https://tailwindcss.com/docs/responsive-design).\n* Only 16 lines. No build step. No dependencies.\n* Pairs well with [htmx](https://htmx.org) and [Surreal](https://github.com/gnat/surreal)\n* Want fewer layers, less complexity. Are aware of the cargo cult. ✈️\n\n✨ Want to also scope your `\u003cscript\u003e` tags? See our companion project [Surreal](https://github.com/gnat/surreal)\n\n## 👁️ How does it look?\n```html\n\u003cdiv\u003e\n    \u003cstyle\u003e\n        me { background: red; } /* ✨ this \u0026 self also work! */\n        me button { background: blue; } /* style child elements inline! */\n    \u003c/style\u003e\n    \u003cbutton\u003eI'm blue\u003c/button\u003e\n\u003c/div\u003e\n```\nSee the [Live Example](https://gnat.github.io/css-scope-inline/example.html)! Then [view source](https://github.com/gnat/css-scope-inline/blob/main/example.html).\n\n## 🌘 How does it work?\n\nThis uses `MutationObserver` to monitor the DOM, and the moment a `\u003cstyle\u003e` tag is seen, it scopes the styles to whatever the parent element is. No flashing or popping. \n\nThis method also leaves your existing styles untouched, allowing you to mix and match at your leisure.\n\n## 🎁 Install\n\n✂️ copy + 📋 paste the snippet into `\u003cscript\u003e` in your `\u003chead\u003e`\n\nOr, [📥 download](https://raw.githubusercontent.com/gnat/css-scope-inline/main/script.js) into your project, and add `\u003cscript src=\"script.js\"\u003e\u003c/script\u003e` in your `\u003chead\u003e`\n\nOr, 🌐 CDN: `\u003cscript src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"\u003e\u003c/script\u003e`\n\n## 🤔 Why consider this over Tailwind CSS?\n\nUse whatever you'd like, but there's a few advantages with this approach over Tailwind, Twind, UnoCSS:\n\n* No [repeated styles](https://tailwindcss.com/docs/reusing-styles) on child elements (..no [@apply](https://tailwindcss.com/docs/reusing-styles#extracting-classes-with-apply), no `[\u0026\u003ething]` on each style).\n* No repeated prefixes for media queries, hover, focus, etc.\n* No visual noise on every `\u003cdiv\u003e`. Use a local `\u003cstyle\u003e` per group.\n* Share syntax between local and external styles. It's just CSS.\n* Regain your \"inspect, play with styles, paste\" workflow in your web browser!\n* No suffering from lost syntax highlighting on properties and units.\n* No high risk of eventually requiring a build step.\n* No chance of [deprecations](https://windicss.org/posts/sunsetting.html). 16 lines is infinitely maintainable.\n* No suffering from FOUC (a flash of unstyled content).\n* Zero friction movement of styles between inline and `.css` files. Just replace `me`\n* No special tooling or plugins to install.\n\n## ⚡ Workflow Tips\n\n* Flat, 1 selector per line can be very short like Tailwind. See the examples.\n* Use just plain CSS variables in your design system.\n* Use the short `@media` queries for responsive design.\n  * Mobile First (flow: **above** breakpoint): **🟢 None (xs)** `sm` `md` `lg` `xl` `xx` 🏁\n  * Desktop First (flow: **below** breakpoint): 🏁 `xs-` `sm-` `md-` `lg-` `xl-` **🟢 None (xx)**\n  * 🟢 = No breakpoint. Default. See the [Live Example](https://gnat.github.io/css-scope-inline/example.html)!\n  * Based on [Tailwind](https://tailwindcss.com/docs/responsive-design) breakpoints. We use `xx` not `2xl` to not break CSS highlighters.\n  * Unlike Tailwind, you can [nest your @media styles](https://developer.chrome.com/articles/css-nesting/#nesting-media)!\n* Positional selectors may be easier using `div[n1]` for `\u003cdiv n1\u003e` instead of `div:nth-child(1)`\n* Try tools like- Auto complete styles: [VSCode](https://code.visualstudio.com/) or [Sublime](https://packagecontrol.io/packages/Emmet)\n\n## 👁️ CSS Scope Inline vs Tailwind CSS Showdowns\n### Basics\nTailwind verbosity goes up with more child elements.\n```html\n\u003c!-- CSS Scope Inline --\u003e\n\u003cdiv\u003e\n    \u003cstyle\u003e\n        me { background: red; }\n        me div { background: green; }\n        me [n1] { background: yellow; }\n        me [n2] { background: blue; }\n    \u003c/style\u003e\n    red\n    \u003cdiv\u003egreen\u003c/div\u003e\n    \u003cdiv\u003egreen\u003c/div\u003e\n    \u003cdiv\u003egreen\u003c/div\u003e\n    \u003cdiv n1\u003eyellow\u003c/div\u003e\n    \u003cdiv n2\u003eblue\u003c/div\u003e\n    \u003cdiv\u003egreen\u003c/div\u003e\n    \u003cdiv\u003egreen\u003c/div\u003e\n\u003c/div\u003e\n\n\u003c!-- Tailwind --\u003e\n\u003cdiv class=\"bg-[red]\"\u003e\n    red\n    \u003cdiv class=\"bg-[green]\"\u003egreen\u003c/div\u003e\n    \u003cdiv class=\"bg-[green]\"\u003egreen\u003c/div\u003e\n    \u003cdiv class=\"bg-[green]\"\u003egreen\u003c/div\u003e\n    \u003cdiv class=\"bg-[yellow]\"\u003eyellow\u003c/div\u003e\n    \u003cdiv class=\"bg-[blue]\"\u003eblue\u003c/div\u003e\n    \u003cdiv class=\"bg-[green]\"\u003egreen\u003c/div\u003e\n    \u003cdiv class=\"bg-[green]\"\u003egreen\u003c/div\u003e\n\u003c/div\u003e\n```\n\n### CSS variables and child elements\nAt first glance, **Tailwind Example 2** looks very promising! Exciting ...but:\n* 🔴 **Every child style requires an explicit selector.**\n  * Tailwinds' shorthand advantages sadly disappear.\n  * Any more child styles added in Tailwind will become longer than vanilla CSS.\n  * This limited example is the best case scenario for Tailwind.\n* 🔴 Not visible on github: **no highlighting for properties and units** begins to be painful.\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003cstyle\u003e\n            :root {\n                --color-1: hsl(0 0% 88%);\n                --color-1-active: hsl(214 20% 70%);\n            }\n        \u003c/style\u003e\n        \u003cscript src=\"https://cdn.tailwindcss.com\"\u003e\u003c/script\u003e\n        \u003cscript src=\"https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js\"\u003e\u003c/script\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003c!-- CSS Scope Inline --\u003e\n        \u003cdiv\u003e\n            \u003cstyle\u003e\n               me { margin:8px 6px; }\n               me div a { display:block; padding:8px 12px; margin:10px 0; background:var(--color-1); border-radius:10px; text-align:center; }\n               me div a:hover { background:var(--color-1-active); color:white; }\n            \u003c/style\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eHome\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eTeam\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eProfile\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eSettings\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eLog Out\u003c/a\u003e\u003c/div\u003e\n        \u003c/div\u003e\n\n        \u003c!-- Tailwind Example 1 --\u003e\n        \u003cdiv class=\"mx-2 my-4\"\u003e\n            \u003cdiv\u003e\u003ca href=\"#\" class=\"block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white\"\u003eHome\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\" class=\"block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white\"\u003eTeam\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\" class=\"block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white\"\u003eProfile\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\" class=\"block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white\"\u003eSettings\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\" class=\"block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white\"\u003eLog Out\u003c/a\u003e\u003c/div\u003e\n        \u003c/div\u003e\n\n        \u003c!-- Tailwind Example 2 --\u003e\n        \u003cdiv class=\"mx-2 my-4\n            [\u0026_div_a]:block [\u0026_div_a]:py-2 [\u0026_div_a]:px-3 [\u0026_div_a]:my-2 [\u0026_div_a]:bg-[--color-1] [\u0026_div_a]:rounded-lg [\u0026_div_a]:text-center\n            [\u0026_div_a:hover]:bg-[--color-1-active] [\u0026_div_a:hover]:text-white\"\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eHome\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eTeam\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eProfile\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eSettings\u003c/a\u003e\u003c/div\u003e\n            \u003cdiv\u003e\u003ca href=\"#\"\u003eLog Out\u003c/a\u003e\u003c/div\u003e\n        \u003c/div\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n## 🔎 Technical FAQ\n* Why do you use `querySelectorAll()` and not just process the `MutationObserver` results directly?\n  * This was indeed the original design; it will work well up until you begin recieving subtrees (ex: DOM swaps with [htmx](https://htmx.org), ajax, jquery, etc.) which requires walking all subtree elements to ensure we do not miss a `\u003cstyle\u003e`. This unfortunately involves re-scanning thousands of repeated elements. This is why `querySelectorAll()` ends up the performance (and simplicity) winner.\n","funding_links":[],"categories":["HTML","Companion Projects"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnat%2Fcss-scope-inline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgnat%2Fcss-scope-inline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgnat%2Fcss-scope-inline/lists"}