Skip to content

gnat/css-scope-inline

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

172 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🌘 CSS Scope Inline

cover (Art by shahabalizadeh)

Why does this exist?

  • 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 for responsive design.
  • Only 16 lines. No build step. No dependencies.
  • Pairs well with htmx and Surreal
  • Want fewer layers, less complexity. Are aware of the cargo cult. ✈️

✨ Want to also scope your <script> tags? See our companion project Surreal

πŸ‘οΈ How does it look?

<div>
    <style>
        me { background: red; } /* ✨ this & self also work! */
        me button { background: blue; } /* style child elements inline! */
    </style>
    <button>I'm blue</button>
</div>

See the Live Example! Then view source.

🌘 How does it work?

This uses MutationObserver 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 leaves your existing styles untouched. Mix in scoped <style> at your leisure.

🎁 Install

βœ‚οΈ 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>

πŸ€” Why consider this over Tailwind CSS?

Use whatever you'd like, but there's a few advantages with this approach over Tailwind, Twind, UnoCSS:

  • No repeat styles on child elements (no more @apply, no [&>thing]).
  • No repeat prefixes for media queries, hover, focus, etc.
  • No visual noise on every <div>. Use <style> on groups of elements.
  • Share real CSS between inline <style> and .css files. Just replace me
  • Regain your "inspect + edit styles + paste" workflow in your browser!
  • No suffering from lost syntax highlighting on properties and units.
  • No high risk of eventually requiring a build step.
  • No chance of deprecations. 16 lines is infinitely maintainable.
  • No suffering from FOUC (a flash of unstyled content).
  • No special binaries, no add-ons, no plugins to install.

⚑ Workflow Tips

  • Use vanilla CSS variables for your design system.
  • Flattened 1 rule = 1 line can be small like Tailwind. See examples
  • Positional selectors may be easier using div[n1] for <div n1> (instead of div:nth-child(1))
  • Child combinator can be very useful for granular control. Example: div>button
  • Use the short @media queries for responsive design.
    • Based on Tailwind breakpoints.
    • 🟒 = Default, no breakpoint required. See the Live Example!
    • Mobile First (flows above default)
      • 🟒 Default xs sm md lg xl xx 🏁 End
    • Desktop First (flows below default)
      • 🏁 End xs- sm- md- lg- xl- xx- 🟒 Default

πŸ‘οΈ CSS Scope Inline vs Tailwind CSS Showdowns

Basics

Tailwind verbosity goes up with more child elements.

<!-- CSS Scope Inline -->
<div>
    <style>
        me { background: red; }
        me div { background: green; }
        me [n1] { background: yellow; }
        me [n2] { background: blue; }
    </style>
    red
    <div>green</div>
    <div>green</div>
    <div>green</div>
    <div n1>yellow</div>
    <div n2>blue</div>
    <div>green</div>
    <div>green</div>
</div>

<!-- Tailwind -->
<div class="bg-[red]">
    red
    <div class="bg-[green]">green</div>
    <div class="bg-[green]">green</div>
    <div class="bg-[green]">green</div>
    <div class="bg-[yellow]">yellow</div>
    <div class="bg-[blue]">blue</div>
    <div class="bg-[green]">green</div>
    <div class="bg-[green]">green</div>
</div>

CSS variables and child elements

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(0 0% 88%);
                --color-1-active: hsl(214 20% 70%);
            }
        </style>
        <script src="https://cdn.tailwindcss.com"></script>
        <script src="https://cdn.jsdelivr.net/gh/gnat/css-scope-inline@main/script.js"></script>
    </head>
    <body>
        <!-- CSS Scope Inline -->
        <div>
            <style>
               me { margin:8px 6px; }
               me div a { display:block; padding:8px 12px; margin:10px 0; background:var(--color-1); border-radius:10px; text-align:center; }
               me div a:hover { background:var(--color-1-active); color:white; }
            </style>
            <div><a href="#">Home</a></div>
            <div><a href="#">Team</a></div>
            <div><a href="#">Profile</a></div>
            <div><a href="#">Settings</a></div>
            <div><a href="#">Log Out</a></div>
        </div>

        <!-- Tailwind Example 1 -->
        <div class="mx-2 my-4">
            <div><a href="#" 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><a href="#" 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><a href="#" 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><a href="#" 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><a href="#" 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 -->
        <div class="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><a href="#">Home</a></div>
            <div><a href="#">Team</a></div>
            <div><a href="#">Profile</a></div>
            <div><a href="#">Settings</a></div>
            <div><a href="#">Log Out</a></div>
        </div>
    </body>
</html>

πŸ”Ž Technical FAQ

  • Why do you use querySelectorAll() and not just process the MutationObserver results directly?
    • This was indeed the original design; it will work well up until you begin recieving subtrees (ex: DOM swaps with htmx, 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 why querySelectorAll() ends up the performance (and simplicity) winner.

🌘 Change Log

1.1.0

  • Started change log.
  • Fix for domain names ending in .me when used url() properties.

Releases

No releases published

Packages

 
 
 

Contributors