method of combining multiple DOM trees into one hierarchy and how these trees interact with each other within a document, thus enabling better composition of the DOM.
At it’s most basic, the Shadow DOM provides a way of creating DOM fragments that that can be injected into parts of the main DOM, but are isolated from changes made in the parent DOM.
Example (the range element)
Browser vendors have utilised the Shadow DOM for some time now with element types such as range and date. When you create an input with a type of date, a hidden shadow DOM fragment is associated with your input element. If you view the source of the element you won’t see any of the DOM elements associated with this by default:
With Chrome you can look to see what the browser is doing under the hood by enabling the user agent shadow DOM setting:
The next time you view the source you should see that your input element actually contains a shadow root that hosts the range Shadow Tree:
Special syntax can be used to style elements within a Shadow Tree. By default any styling applied to the hosting document does not apply to the Shadow Tree, and visa-versa. This will resolve a world of pain for component vendors who need to make their control work with a huge number of different sites.
In the following example we’ll combine the Shadow DOM with a Custom Element in order to create a simple user-profile badge component:
Firstly, we create a custom element object based on the DOM Element Prototype. We then set up a hook into a callback that will be fired when the custom element is registered.
There are four callbacks available:
- createdCallback: Called when an element is registered.
- attachedCallback: Called when an element is inserted into the DOM.
- DetachedCallback: Called when an element is removed from the DOM.
- attributeChangedCallback: Called when an attribute on an element is changed, added or removed.
For now, the only one that we need to worry about is the createdCallback. We hook into this callback, create the Shadow Root which is the host element. In this case we immediately set the innerHTML of this element. This is a fairly crude example. Realistically you would use the power of templates and HTML Imports to shift all of this HTML and CSS code into a more sensible location.
The last thing that we need to do is register the element. Which will in turn fire the callback that we created above. This lets the browse know about the element that we’ve defined, so that it will appropriately interpret the element’s usage in the corresponding HTML file. One important thing to note at this point is that custom elements must contain a ‘–‘ character in the name. This caught me out initially.
With this in place we can consume the element in our HTML file:
Which in turn renders the expected user profile element:
We now have everything associated with our custom element self contained within it’s own shadow root. In a way it looks like we’ve got our own mini DOM inside of our main HTML document, which is pretty close conceptually speaking to what is actually happening.
The sample code for this blog post is available on Github.