- Notifications
You must be signed in to change notification settings - Fork13
🌘 Scope your inline style tags in pure vanilla CSS! Only 16 lines. No build. No dependencies.
License
gnat/css-scope-inline
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
(Art byshahabalizadeh)
- You want an easy inline vanilla CSS experience without Tailwind CSS.
- Hate creating unique class names over.. and over.. to use once.
- You want to co-locate your styles for ⚡️Locality of Behavior (LoB)
- You wish
this
would work in<style>
tags. - Want all CSS features:Nesting, animations. Get scoped
@keyframes
! - You wish
@media
queries were shorter forresponsive design. - Only 16 lines. No build step. No dependencies.
- Pairs well withhtmx andSurreal
- Want fewer layers, less complexity. Are aware of the cargo cult.
✈️
✨ Want to also scope your<script>
tags? See our companion projectSurreal
<div><style>me {background: red; }/* ✨ this & self also work! */mebutton {background: blue; }/* style child elements inline! */</style><button>I'm blue</button></div>
See theLive Example! Thenview source.
This usesMutationObserver
to monitor the DOM, and the moment a<style>
tag is seen, it scopes the styles to whatever the parent element is. No flashing or popping.
This method also leaves your existing styles untouched, allowing you to mix and match at your leisure.
✂️ copy + 📋 paste the snippet into<script>
in your<head>
Or,📥 download into your project, and add<script src="script.js"></script>
in your<head>
Or, 🌐 CDN:<script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
Use whatever you'd like, but there's a few advantages with this approach over Tailwind, Twind, UnoCSS:
- Norepeated styles on child elements (..no@apply, no
[&>thing]
on each style). - No repeated prefixes for media queries, hover, focus, etc.
- No visual noise on every
<div>
. Use a local<style>
per group. - Share syntax between local and external styles. It's just CSS.
- Regain your "inspect, play with styles, paste" workflow in your web browser!
- No suffering from lost syntax highlighting on properties and units.
- No high risk of eventually requiring a build step.
- No chance ofdeprecations. 16 lines is infinitely maintainable.
- No suffering from FOUC (a flash of unstyled content).
- Zero friction movement of styles between inline and
.css
files. Just replaceme
- No special tooling or plugins to install.
- Flat, 1 selector per line can be very short like Tailwind. See the examples.
- Use just plain CSS variables in your design system.
- Use the short
@media
queries for responsive design.- Mobile First (flow:above breakpoint):🟢 None (xs)
sm
md
lg
xl
xx
🏁 - Desktop First (flow:below breakpoint): 🏁
xs-
sm-
md-
lg-
xl-
🟢 None (xx) - 🟢 = No breakpoint. Default. See theLive Example!
- Based onTailwind breakpoints. We use
xx
not2xl
to not break CSS highlighters. - Unlike Tailwind, you cannest your @media styles!
- Mobile First (flow:above breakpoint):🟢 None (xs)
- Positional selectors may be easier using
div[n1]
for<div n1>
instead ofdiv:nth-child(1)
- Try tools like- Auto complete styles:VSCode orSublime
Tailwind verbosity goes up with more child elements.
<!-- CSS Scope Inline --><div><style>me {background: red; }mediv {background: green; }me [n1] {background: yellow; }me [n2] {background: blue; }</style> red<div>green</div><div>green</div><div>green</div><divn1>yellow</div><divn2>blue</div><div>green</div><div>green</div></div><!-- Tailwind --><divclass="bg-[red]"> red<divclass="bg-[green]">green</div><divclass="bg-[green]">green</div><divclass="bg-[green]">green</div><divclass="bg-[yellow]">yellow</div><divclass="bg-[blue]">blue</div><divclass="bg-[green]">green</div><divclass="bg-[green]">green</div></div>
At first glance,Tailwind Example 2 looks very promising! Exciting ...but:
- 🔴Every child style requires an explicit selector.
- Tailwinds' shorthand advantages sadly disappear.
- Any more child styles added in Tailwind will become longer than vanilla CSS.
- This limited example is the best case scenario for Tailwind.
- 🔴 Not visible on github:no highlighting for properties and units begins to be painful.
<!doctype html><html><head><style>:root {--color-1:hsl(00%88%);--color-1-active:hsl(21420%70%); }</style><scriptsrc="https://cdn.tailwindcss.com"></script><scriptsrc="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script></head><body><!-- CSS Scope Inline --><div><style>me {margin:8px6px; }mediva {display:block;padding:8px12px;margin:10px0;background:var(--color-1);border-radius:10px;text-align:center; }mediva:hover {background:var(--color-1-active);color:white; }</style><div><ahref="#">Home</a></div><div><ahref="#">Team</a></div><div><ahref="#">Profile</a></div><div><ahref="#">Settings</a></div><div><ahref="#">Log Out</a></div></div><!-- Tailwind Example 1 --><divclass="mx-2 my-4"><div><ahref="#"class="block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white">Home</a></div><div><ahref="#"class="block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white">Team</a></div><div><ahref="#"class="block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white">Profile</a></div><div><ahref="#"class="block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white">Settings</a></div><div><ahref="#"class="block py-2 px-3 my-2 bg-[--color-1] rounded-lg text-center hover:bg-[--color-1-active] hover:text-white">Log Out</a></div></div><!-- Tailwind Example 2 --><divclass="mx-2 my-4 [&_div_a]:block [&_div_a]:py-2 [&_div_a]:px-3 [&_div_a]:my-2 [&_div_a]:bg-[--color-1] [&_div_a]:rounded-lg [&_div_a]:text-center [&_div_a:hover]:bg-[--color-1-active] [&_div_a:hover]:text-white"><div><ahref="#">Home</a></div><div><ahref="#">Team</a></div><div><ahref="#">Profile</a></div><div><ahref="#">Settings</a></div><div><ahref="#">Log Out</a></div></div></body></html>
- Why do you use
querySelectorAll()
and not just process theMutationObserver
results directly?- This was indeed the original design; it will work well up until you begin recieving subtrees (ex: DOM swaps withhtmx, ajax, jquery, etc.) which requires walking all subtree elements to ensure we do not miss a
<style>
. This unfortunately involves re-scanning thousands of repeated elements. This is whyquerySelectorAll()
ends up the performance (and simplicity) winner.
- This was indeed the original design; it will work well up until you begin recieving subtrees (ex: DOM swaps withhtmx, ajax, jquery, etc.) which requires walking all subtree elements to ensure we do not miss a