There are multiple use cases in web development when it makes sense to encapsulate part of a website and make it independent from every other element in the HTML. A good example is when you integrate an external application into your page. Let's say this example application is a chatbot.
You would probably be surprised (and furious) if adding the chatbot changed your pages' overall design. However, the creators of the chatbot would feel the same if their chat window was influenced by the websites where it is integrated.
Fortunately, a concept in web development aims to solve this very problem: the shadow DOM.
In one sentence, the shadow DOM is an independent DOM tree you can attach to an element, and it will ensure that the internals of the tree are hidden from the rest of the page.
This also means that you can (and have to) style the internals of the shadow DOM separately as CSS outside of it has no influence on the internals.
And being a modern frontend developer, you might ask the following question:
Can I use Tailwind inside the shadow DOM?
Let's say this post wouldn't have been born if things were so easy. Luckily for you, I already solved this problem in my latest project, so just keep reading.
Using Tailwind in The Shadow DOM
There has been a long discussion on the GitHub page of TailwindCSS going on for 4 years already, even though it has a (suboptimal) answer at the very top.
The most recent messages are pretty similar to what I have used to solve the problem. There were two approaches coming to my mind.
Option 1: Unique ID on The Root Element
Suppose you are only using the shadow DOM because you want to separate your application's style from the style of the websites where it will be integrated. In that case, you can add a unique ID to the root element of your app, and set that ID as a custom prefix in Tailwind. This way, styles will only be applied to child elements of the shadow root (the root element in your shadow DOM).
In this case, you don't even need to use the shadow DOM. However, this approach isn't as clean as the second, and by not using the shadow DOM, you risk leaking your style and influencing the rest of the app, even though the chances of that are pretty low. Here is a better option, though.
Option 2: Append a Style Element Inside The Shadow DOM
The solution I went with was to manually build the output CSS with Tailwind and append the content of it to a child <style>
element inside the shadow DOM.
To build the output CSS, you can add the following script to your package.json
, or run it manually:
This command will collect all your application's CSS utility classes and put them into dist/style.css
. Once you append a <style>
element to the shadow DOM with the content of this file, elements inside the shadow DOM will use the utility classes present in the <style>
, but elements outside of the shadow root won't be influenced by it.
A Real-Life Shadow DOM Example
Here is the example I mentioned in the introduction in real life. I am currently building a self-service chatbot platform, and it is crucial that the design of the chat window is independent of any website where users will integrate it. Of course, the chatbot can be styled to match any website's aesthetics, but that's a feature. However, style leakage in either direction would be a bug.
To solve this, I am utilizing the shadow DOM in my application. The CSS is automatically built and deployed into a CDN, from where the application will download its content on each load and append it as a <style>
element inside the shadow root.
However, to make development faster, I only use the shadow DOM in production. This way, I can enjoy hot-reloading while working on the application, and I don't have to build the CSS for each modification.
You can check out how to create a shadow DOM in React and append the <style>
element with Tailwind's utility classes in the chatbot's open-source repository.