Napkin explained #1: Polymer’s 50 shades of DOM

Andrei Notna
4 min readSep 6, 2015

One of the most confusing things when starting to work with Polymer (or WebComponents) is the fact that you no longer have just one DOM tree. You have more. And you have to keep track in your head which one you’re working with.

The three trees model sits at the core of Polymer, and it’s the “magic sauce” that enables encapsulation of styles and scripts and construction of components that can, in theory, be reused from one web application to another via a simple import-copy-paste process.

So let’s start from how you start designing a custom Polymer component:

<dom-module id=”my-component”>
<style>
<!-- CSS for this element: have *no effect outside*!-->
</style>
<template>
<!-— Local DOM -->
...
<content>
<!-- Light DOM: element-s children get rendered here -->
</content>
<template> <script>
Polymer({
is: "my component",
// ...component code
});
</script>
</dom-module>

You see now that first you have:

1. Local DOM

Local DOM is made up of the components of your <template> tag.
And there are two ways Local DOM can be implemented:

  • Local DOM implemented as Shadow DOM — this is the “real deal” version that actually insulates its contents from all outside CSS styles, even “raw”, un-preprocessed styles, like a bootstrap.css or app.css file that contains regulr plain old CSS.
  • Local DOM implemented as Shady DOM — this is the default version enabled in Polymer 1.0, and it works in older browsers too. It only insulates the insides of your polymer components from styles that are properly enclosed in <style is=”custom-style”> tags (or in other components) that properly pre-processes their styles. This is why it’s called a “lightweight polyfill”: it emulates Shadow DOM, but it doesn’t try to do it 100%, because this would lead to tons of complex unmaintainable code an lots of obscure bugs, as Polymer 0.5 showed.

If you’re looking at a polymer components using your browser’s inspector, this is how you’d seed the difference between the two:

Shady DOM vs. Shadow DOM

Shady DOM looks just like a regular piece of a DOM tree, with some extra classes like style-scope that allow the magic to happen. Shadow DOM otoh is obviously a special browser feature.

2. Light DOM

Now, you know that inside a custom polymer component you can add children, and you can render the children inside <template> using <content>. So you can have something like this:

<my-custom-element>
<h4>Funcky section title</h4>
<p>Text funk lorem impsum…</p>
</my-custom-element>

…and inside you component’s <template> something like this:

<dom-module id=”my-cutom-element”>
...
<template>
<div id=”funkySection”>
<span class=”funky-section-drag-handle”></span>
<content></content>
</div>
</template>
...
</dom-module>

Light DOM in simply this rendered content, the <h4> and <p> in the above example.

Summarizing what we went through so far, we have something like this:

Now, there’s still one more thing we’re interested in. The filled-heads arrows in the diagram above show what CSS rules apply to which objects. But what if you want to apply styling tweaks to the “guts” of a polymer component, i.e. it’s local dom and light dom, from an external style.

There are two answers to this question:

  1. Don’t do it because it’s a bad idea: it breaks the whole encapsulation that Polymer was created to provide in the first place!
  2. Use /deep/ and, when you need more “surgical precision”, use ::shadow

This is how it works:

my-custom-element::shadow .some-selector-from-local-dom {
/* this will apply to nodes of the local (shady/shadow) dom
of my-custom-element (but NOT TO LIGHT DOM NODES!) */
}
my-custom-element /deep/ .some-selector-from-local-dom-of-child-of-child-of...-child-of-my-custom-element {
/* this will apply to nodes of the local (shady/shadow) dom
of my-custom-element AND ALL ITS DESCENDENTS */
}
/* and from these you can obviously guess that you can write
the "nuclear options" variants: */
body /deep/ .whatever {}
html /deep/ .whatever {}

The last two obviously do what you’d expect: they completely ignore that you are using Polymer or WebComponents… So if you find yourself using them very often, you should either change the way you write your front-end code, or ditch Polymer and chooses something more suited for your workflow.

The next step if you’ve gotten this far would be to re-read Polymer’s docs on Local DOM and Styling local DOM. And, unfortunately, Polymer’s docs are rarely enough. Yo should also go over Shadow DOM 101 and Shadow DOM 201 if you’re using Polymer or anything WebComponents based.

I hope this helped all new to Polymer and it’s perversions to better make sense of things. Things may be a bit confusing, but they are how they are because of the eternal reason: browsers need to maintain compatibility with all the mistakes of their past. So better learn to love these twisted ways… because you’d also find them if you dig at the bottom of full-fledged frameworks like Ember.js, meta-frameworks like Angular and “UI systems” like React. They are simply better at hiding the horrors in theirs dark basements, instead of leaving them in the open for everyone to see like Polymer does…

--

--

Andrei Notna

Machine-learning engineer. All-over-the-f-stack programmer. Quasi-polymath. Aspiring writer. When I grow up/old I'll probably become an entrepreneur.