We are all fully aware of the Document Object Model (DOM). Thanks to it, we can manipulate the structure and the content of an HTML page via JavaScript in an effective way.
Of course, the DOM is not free from defects. One of them, for example, is the lack of encapsulation, that is the possibility to protect a part of the document structure from global processing, such as CSS rules application and JavaScript manipulation.
In this article, we will analyze an example of this issue, how it is traditionally tackled and how we can resolve it by using the Shadow DOM, a standard feature that has been around for a while but is not as well-known by front-end developers.
Consider the following JavaScript code:
Here, we define an object with a single method, renderSearchBox(). This method takes the identifier of an HTML element as its argument and appends to it the elements needed to build up a search textbox like the one shown below:
This search textbox is intended to allow the user to find glossary definitions from an online service. For simplicity, we have embedded the term definitions in the code.
When the user inserts a term in the textbox and clicks the search button, the form.onsubmit() event listener is executed. This function simply shows the definition associated with the inserted term, if any, otherwise it shows an appropriate message.
The following picture shows what the user will see when they submit the HTML term into the search text box:
You can use the myGlossary object in an HTML page:
As you can see, we imported the script from myGlossary.js file and invoked the renderSearchBox() method by passing the divBox string, as the identifier of the only div element in the page.
You can try this code on CodePen.
The search box created by the script shown above works as expected. It is also self-contained enough to be re-used in other projects.
But what happens if you use the search box inside a page with a markup?
In this page, you notice a CSS rule re-defining the colour of the h4 element. Of course, the page designer meant to customize the welcome message. However, this rule also affects the title of the imported search box:
Since the DOM is a global entity, the CSS rule shown above affects any h4 element in the page, even the ones dynamically added from an external script. You have no way of protecting the specific h4 element defined when building the search box. You cannot encapsulate it if you just rely on the DOM.
But this issue is not only related to the CSS rule application. Imagine what happens if somewhere on the page you have the following JavaScript statement:
Your search textbox magically disappears:
Try this code on CodePen.
You cannot prevent the txtTerm id from being used elsewhere on the page. And you cannot control what any external code may do, intentionally or not, with the DOM elements you created for your search box.
Again, the DOM is a global entity: any element you add to it is publicly accessible which means conflicts may happen.
The most common solution to get around this problem is to embed the portion of DOM representing your component in a separate DOM, i.e. in a separate HTML page. This page is then appended to the main page via an iframe. This is the approach used by Google to allow developers to embed its maps and its videos from YouTube and Twitter.
Let’s take a look at how to implement this idea. Rearrange the previous code like this:
The first difference, with respect to the previous version, is the creation of an iframe element:
The second difference is the way you append the portion of DOM representing the search box to the external element. With this approach, you don’t directly append the box element to the external element. You append the iframe element to the external element and then append the box element to the body of the document associated with the iframe. The code would look something like this:
With this approach, you will no longer get the conflicts you experienced with the previous version of the search box.
You can try this on CodePen.
In the iframe-based approach, you should have noticed a couple of assignments concerning the style of the iframe. Let’s recall these two assignments in the following:
These assignments define the border thickness and the height of the iframe itself. They are fundamental to provide the illusion that the search box is integrated with the main page.
But while the first instruction fulfills its task well, the second may be insufficient. In fact, you may have a chance that the size of the iframe doesn’t fit the size of the document, so you could have an effect similar to the following:
This is an ugly effect and you should avoid it.
Iframes are not very responsive. In addition, iframes are designed to embed another full document in the current page. This leads to more complexity when we want to pass data from the parent document to the child document, for example, in order to configure some elements in the child document.
A better solution to manage the encapsulation of a portion of the DOM inside the DOM of an HTML page is to use the Shadow DOM. This is a set of standard APIs enabling the encapsulation at the DOM level by allowing you to create a sort of private DOM for an HTML element.
Let’s see how to use the Shadow DOM to avoid the issues of both the global DOM and the iframes. Rewrite the JavaScript code implementing the search box as follows:
In this case, the only difference with respect to the original version of the script consists of the following statements:
Instead of directly attaching the box element to the external element, we first create a Shadow DOM to the external element via attachShadow(). This method is available for almost any HTML element and it generates the root node for the Shadow DOM of the element.
This root node is then accessible through the shadowRoot property. In the example above, we appended to the shadowRoot of the external element the box element representing the search box. These two simple instructions grant us protection against conflicts and undesired effects when the search box is included in an arbitrary HTML page. You can try this code on CodePen.
You may notice that we passed a literal object as an argument to the attachShadow() method. This object allows us to specify the creation mode of the Shadow DOM. In our example, we set the value open as the Shadow DOM mode. This means that the Shadow DOM will be hidden to standard global DOM manipulation such as CSS rules applications, node selection and the like.
However, the Shadow DOM is exposed to the outside world, so its elements will be accessible via JavaScript. In other words, you can access the Shadow DOM of the search box from any page with something like this:
This code replaces the text of the label associated with the search textbox.
If you don’t want this to happen, you may choose to set the closed value:
In this case, the Shadow DOM will not be accessible at all.
Iframes can still be useful when they do the work they were born for: embedding an independent external page in the current page. However, we have now seen how the Shadow DOM can replace the typical encapsulation workaround based on iframes which can have a lot of benefits.
As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording everything that happens in your web app, mobile app, or website. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.
Modernize how you debug web and mobile apps — start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowExplore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.
Build a real-time image background remover in Vue using Transformers.js and WebGPU for client-side processing with privacy and efficiency.
Optimize search parameter handling in React and Next.js with nuqs for SEO-friendly, shareable URLs and a better user experience.