@oy3o/css-scope-polyfill
v1.2.0
Published
A lightweight, zero-dependency polyfill for the CSS @scope at-rule with MutationObserver support.
Maintainers
Readme
CSS @scope Polyfill
Bring the future of CSS encapsulation to today's browsers.
A lightweight, zero-dependency runtime polyfill for the CSS@scopeat-rule.
Why this?
The CSS @scope rule is a game-changer for component architecture, allowing you to select elements specifically within a DOM subtree. However, browser support is still catching up.
This polyfill bridges the gap. It observes your DOM, parses your CSS, and applies the scoping logic dynamically—preserving your ability to write modern, clean CSS today.
Features
- Native Syntax Support: Writes exactly like native CSS. No custom classes required.
- Live DOM Monitoring: Automatically detects new
<style>tags or lazy-loaded components viaMutationObserver. - Intelligent Scoping:
- Supports
:scopepseudo-class. - Supports
&nesting selectors. - Handles complex selectors like
:is(),:where(), and:not()correctly.
- Supports
- Dual-Mode Architecture:
- Zero-Config: Drop it in via CDN, and it just works.
- Framework-Ready: Pure ESM export with Dependency Injection support for advanced integration.
Installation
npm install @oy3o/css-scope-polyfillOr use your favorite package manager (pnpm, yarn).
Usage
1. The "Drop-in" Mode (CDN)
Best for static sites or quick prototypes.
Simply add the script to your <head>. It will automatically scan for @scope rules and watch for changes.
<!-- Via unpkg -->
<script src="https://unpkg.com/@oy3o/css-scope-polyfill"></script>
<!-- OR via jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/@oy3o/css-scope-polyfill"></script>Manual Control (Optional)
If you want to load the script but prevent it from running automatically (e.g., to configure it later), add the data-manual attribute:
<script src="..." data-manual></script>
<script>
// Start it manually when you are ready
window.CSSScopePolyfill();
</script>2. The "Architect" Mode (ESM / Frameworks)
Best for React, Vue, Vite, Webpack, or internal frameworks.
Import the pure logic. It does not start automatically, giving you full control over the lifecycle.
import ScopePolyfill from '@oy3o/css-scope-polyfill';
// Basic usage: Start with default MutationObserver
ScopePolyfill();Advanced: Dependency Injection (BYO Observer)
If your framework already has an event system (like a global EventBus or a virtual DOM patcher), you can inject a custom watcher to avoid running a duplicate MutationObserver.
import ScopePolyfill from '@oy3o/css-scope-polyfill';
import { events } from './my-framework';
/**
* @typedef {object} EventSystem
* @property {(callback: (mutations: {added: Set<Element>, removed: Set<Element>}) => void) => void} onMutation
*/
ScopePolyfill(events);Supported Syntax
Basic Scoping
/* Input */
@scope (.card) {
:scope {
border: 1px solid red; /* Applies to .card itself */
}
img {
border-radius: 50%; /* Only applies to img INSIDE .card */
}
}Nesting & Pseudo-classes
/* Input */
@scope (.sidebar) {
.tab {
background: #ccc;
/* Nesting support */
&:hover {
background: #fff;
}
}
/* Complex selectors work too */
:scope > .content :is(h1, h2) {
color: blue;
}
}Limitations & Trade-offs (Red Team Analysis)
- CORS (Cross-Origin Resource Sharing):
Since the polyfill must fetch external CSS files to parse them,
<link href="...">pointing to a different domain must serve correct CORS headers (Access-Control-Allow-Origin: *). - Flash of Unstyled Content (FOUC): As a runtime polyfill, there is a slight delay between the CSS loading and the polyfill rewriting the rules. For critical path CSS, consider server-side transformation if possible.
- Specific Syntax:
Currently supports the "Root Scoping" syntax (
@scope (.root) { ... }). Support for the "Donut Scoping" (lower boundaryto (...)) is experimental.
License
MIT © oy3o
