Deprecated: Grav\Plugin\FormPlugin::onTwigVariables(): Implicitly marking parameter $event as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/form.php on line 416

Deprecated: Grav\Plugin\FormPlugin::form(): Implicitly marking parameter $page as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/form.php on line 1179

Deprecated: Grav\Plugin\FormPlugin::createForm(): Implicitly marking parameter $name as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/form.php on line 1231

Deprecated: Grav\Plugin\FormPlugin::createForm(): Implicitly marking parameter $form as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/form.php on line 1231

Deprecated: Grav\Plugin\Login\Login::checkLoginRateLimit(): Implicitly marking parameter $ip as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 314

Deprecated: Grav\Plugin\Login\Login::resetLoginRateLimit(): Implicitly marking parameter $ip as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 333

Deprecated: Grav\Plugin\Login\Login::getIpKey(): Implicitly marking parameter $ip as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 344

Deprecated: Grav\Plugin\Login\Login::sendInviteEmail(): Implicitly marking parameter $message as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 523

Deprecated: Grav\Plugin\Login\Login::sendInviteEmail(): Implicitly marking parameter $user as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 523

Deprecated: Grav\Plugin\Login\Login::getPage(): Implicitly marking parameter $route as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 626

Deprecated: Grav\Plugin\Login\Login::getPage(): Implicitly marking parameter $page as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 626

Deprecated: Grav\Plugin\Login\Login::addPage(): Implicitly marking parameter $route as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 666

Deprecated: Grav\Plugin\Login\Login::addPage(): Implicitly marking parameter $page as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 666

Deprecated: Grav\Plugin\Login\Login::getRoute(): Implicitly marking parameter $enabled as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 688

Deprecated: Grav\Plugin\Login\Login::isUserAuthorizedForPage(): Implicitly marking parameter $config as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Login.php on line 735

Deprecated: Grav\Plugin\Email\Email::message(): Implicitly marking parameter $subject as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/email/classes/Email.php on line 72

Deprecated: Grav\Plugin\Email\Email::message(): Implicitly marking parameter $body as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/email/classes/Email.php on line 72

Deprecated: Grav\Plugin\Email\Email::message(): Implicitly marking parameter $contentType as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/email/classes/Email.php on line 72

Deprecated: Grav\Plugin\Email\Email::message(): Implicitly marking parameter $charset as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/email/classes/Email.php on line 72

Deprecated: Grav\Plugin\Email\Email::send(): Implicitly marking parameter $envelope as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/email/classes/Email.php on line 92

Deprecated: Grav\Plugin\Form\Form::getFileUploadError(): Implicitly marking parameter $language as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/classes/Form.php on line 751

Deprecated: Grav\Plugin\Form\Form::removeFlashUpload(): Implicitly marking parameter $field as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/classes/Form.php on line 1259

Deprecated: Mf2\Parser::parse(): Implicitly marking parameter $context as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/webmention/classes/Parser.php on line 1100

Deprecated: Grav\Plugin\Form\TwigExtension::includeFormField(): Implicitly marking parameter $default as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/classes/TwigExtension.php on line 146

Deprecated: Grav\Plugin\Login\Events\PageAuthorizeEvent::__construct(): Implicitly marking parameter $config as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/login/classes/Events/PageAuthorizeEvent.php on line 40

Deprecated: Grav\Plugin\Form\Forms::createPageForm(): Implicitly marking parameter $name as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/classes/Forms.php on line 65

Deprecated: Grav\Plugin\Form\Forms::createPageForm(): Implicitly marking parameter $form as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/user/plugins/form/classes/Forms.php on line 65
How to Scope CSS by Moving an Element to the Shadow DOM | Matthew Miner's Blog

Matthew Miner's Basic-ish BlogMatthew Miner's Blog

Sometimes I might say something

How to Scope CSS by Moving an Element to the Shadow DOM

PROGRAMMING WEB DEVELOPMENT HTML CSS JAVASCRIPT SHADOW DOM TECH

Deprecated: Parsedown::blockSetextHeader(): Implicitly marking parameter $Block as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/vendor/erusev/parsedown/Parsedown.php on line 715

Deprecated: Parsedown::blockTable(): Implicitly marking parameter $Block as nullable is deprecated, the explicit nullable type must be used instead in /home/public/blog/grav-admin/vendor/erusev/parsedown/Parsedown.php on line 853
Bakura demonstrating how to banish things to the shadow DOM in Yu-Gi-Oh!

(tl;dr: Jump to the actual moving)

Scope is an important concept in programming. It allows you to just write small sections of code to do what you need them to do without having to worry about something else in your code using the same name and messing everything up. Declarations (such as of variables or functions) will only be visible to code also in the same scope, usually some block of code.

MDN has a good example of the effects of scope in Javascript:

let x = 1;
if (x === 1) {
    let x = 2; // Not an error because it's in a different scope
    console.log(x); // Logs 2
}
console.log(x); // Logs 1

Sadly this sort of functionality has been sorely lacking in HTML and CSS. One has either had to carefully check that he doesn't reuse any class names on his site or one's styles could really mess up anothers. Special frameworks have even been written to dynamically generate class names to ensure there are no conflicts. This can work if you are fine learning the whole framework, taking on that overhead, and having all your elements having a bunch of these "col-md-offset-4"-like classes; but it's not always ideal. For a while there was the bright hope of the scoped attribute. This allowed one to do this:

<p>Hello unstyled world!</p>
<div>
    <style scoped>
        p {
            color: blue;
        }
    </style>
    <p>I'm blue (Da Ba Dee Da Ba Die)</p>
</div>

However, scoped was tragically snuffed out years ago due to Chrome refusing to implement it. Instead we got...the shadow DOM.

Technically, shadow DOM already existed. Browsers have long used shadow DOMs under the hood to style things like <input> sliders, but only very recently have users been able to use them. They are now implemented in all three major browsers, very useful, and...not very intuitive to use.

"What even is a shadow DOM?" you may ask. Well, a shadow DOM is basically a portion of your page that cannot affect anything elsewhere. It's encapsulated and therefore scoped. You can attach a special node called a shadow root to (almost) any node on your webpage, and you can then attach other elements to this shadow root. This shadow tree will render without affecting anything else. To actually implement this, one is required to use JavaScript for, uh, some reason probably. You do it like this:

<p>Hello unstyled world!</p>
<div id="shadow-host"></div>
<script>
    let shadowRoot = document.getElementById("shadow-host").attachShadow({mode: 'open'});
    let shadowRealm = document.createElement("div");
    shadowRoot.appendChild(shadowRealm);
    let bonz = document.createElement("q");
    bonz.innerHTML = "Ahhh help me";
    shadowRealm.appendChild(bonz);

    shadowRealm.style.backgroundColor = "purple";
    bonz.style.color = "transparent";
    bonz.style.textShadow = "0 0 1px white";
</script>

There, a working shadow DOM. I also added some styling on in there. That again has to be done with JavaScript...
Note that you can't actually style the shadow root itself. There's actually very little that you can do to it.

Of course, setting all CSS through JavaScript would be terrible. I don't even think you can style the shadow root through JS. Thankfully, even more recently, external stylesheets have been allowed to be linked within shadow DOMs, meaning one can now just do this to import a whole stylesheet just for a section of the page:

let shadowLink = shadowRoot.appendChild(document.createElement('link'));
shadowLink.href = "/style.css";
shadowLink.rel = "stylesheet";
shadowLink.type = "text/css";

For my use though, I wanted to generate the page server-side and just load the isolated CSS afterwards. For this, since JavaScript is inexplicably required, I just set a class on the things I want encapsulated and then looped through those with JavaScript and banished them to the shadow DOM:

<details class="banish-me">
    <summary>Open me!</summary>
    <p>Hello! :D</p>
</details>
<script>
    document.querySelectorAll('.banish-me').forEach(x => {
        let shadowHost = document.createElement('div');
        x.parentNode.replaceChild(shadowHost, x);
        let shadowRoot = shadowHost.attachShadow({mode: 'open'});
        shadowRoot.appendChild(x);
        let shadowLink = shadow.appendChild(document.createElement('link'));
        shadowLink.href = "https://www.w3.org/StyleSheets/Core/Midnight";
        shadowLink.rel = "stylesheet";
        shadowLink.type = "text/css";
    }
</script>

There, now this works nicely. This technique lets each <details> have its own external stylesheet loaded just in its scope. Of course, as long as the user has a new enough browser and has JavaScript enabled.

Previous Post