Digging Deeper into Why Web Components and Tailwind CSS Don't Play Well Together

Introduction
Hello! My name is Kameyama, and I'm a web engineer in the Project Promotion Group at KINTO Technologies. I'm currently studying frontend development.
In modern web development, **component-oriented ** architecture has become the standard. By breaking the UI into reusable components, efficiency and maintainability can be improved. Web Components and Tailwind CSS are both powerful tools that support component-based frontend development.
Web Components is a technology that's been gaining attention in recent years. It allows you to create reusable, encapsulated custom elements based on web standards. Tailwind CSS, on the other hand, is a CSS framework that offers fast UI styling through a utility-first approach. The recent release of Tailwind CSS v4 has brought even better performance, and it continues to receive active updates.
At first glance, these technologies may seem like a good match. The idea of "encapsulated markup and logic per component (with Web Components)" combined with "easy styling with utility classes (with Tailwind CSS)" sounded ideal, I thought.
But once I started developing, things wouldn't work out the way I expected. As I dug deeper, I found that the Shadow DOM, a core part of Web Components, and the styling mechanism of Tailwind CSS are fundamentally at odds with each other. In this article, I'll share what I've learned about why these two don't work well together, especially from the perspective of the Shadow DOM, and why combining them is generally discouraged.
About Web Components
What is Shadow DOM?
First of all, Web Components are mainly made up of the following three technologies:
- Custom Elements: An API for defining your own HTML elements (e.g.,
<my-button>
) - Shadow DOM: A technology that isolates (encapsulates) a component's internal DOM tree and styles from the outside world.
- HTML Templates:
<template>
and<slot>
elements to hold reusable markup fragments
Among these, the existence of Shadow DOM plays a key role in why Web Components and Tailwind CSS don't work well together.
When you attach Shadow DOM to an element, that element becomes a Shadow Host and contains a hidden internal DOM tree called Shadow Tree. As a general rule, styles applied to elements inside a shadow tree are not affected by anything outside of it, such as the main document or a parent shadow tree. Likewise, styles defined outside the shadow tree generally won't apply to its internal elements either.
This mechanism provides powerful style encapsulation, preventing a component's styles from being affected by external CSS and keeping internal styles from leaking outside. This helps ensure that your components won't break or look inconsistent, even in environments where multiple CSS design approaches are used.
Here is an example of source code showing how the two are used together.
class MyStyledComponent extends HTMLElement {
constructor() {
super();
// Shadow DOMをアタッチ(openモードで外部からアクセス可能に)
const shadowRoot = this.attachShadow({ mode: 'open' });
// Shadow DOM内部のHTML構造
const template = document.createElement('template');
template.innerHTML = `
<div class="container mx-auto p-4 bg-blue-200"> // Tailwindクラスを使用
<p class="text-xl font-bold text-gray-800">Hello from Shadow DOM!</p> // Tailwindクラスを使用
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Click me
</button>
</div>
`;
shadowRoot.appendChild(template.content.cloneNode(true));
// ★ ここが問題 ★ Shadow DOM内部にスタイルを適用するには...?
// 外部のスタイルシートは原則届かない
}
}
customElements.define('my-styled-component', MyStyledComponent);
As shown in the example above, even if you use Tailwind classes like <div class="container mx-auto p-4 bg-blue-200">
inside the Shadow DOM of MyStyledComponent
, those styles won't be applied by default.
About Tailwind CSS
Utility-First and Global CSS
Tailwind CSS takes an approach to building UIs quickly by writing low-level utility classes such as flex
, pt-4
, and text-blue-500
directly into HTML.
During the build process, Tailwind scans your project's HTML, JavaScript, TypeScript files, etc. and generates CSS rules for any utility classes used. The generated CSS is typically output as a single global stylesheet, which is then loaded into the HTML document, often in the <head>
.
For example, if your HTML contains something like <div class="flex pt-4">
, Tailwind will generate the following CSS rules and include them in the global stylesheet:
/* Tailwindによって生成されるCSS(の例) */
.flex {
display: flex;
}
.pt-4 {
padding-top: 1rem;
}
A key point about Tailwind's styling mechanism is that CSS rules are defined in the global scope.
A Hopeless Incompatibility
Shadow DOM Encapsulation vs. Tailwind's Global Styles
Here's the crux of the problem.
- Shadow DOM encapsulates internal elements to prevent external styles from being applied to them.
- Tailwind CSS, on the other hand, generates CSS rules in the global scope based on the utility classes you use.
There is a clear contradiction between the two. CSS rules like .flex { display: flex; }
generated globally by Tailwind do not cross the Shadow DOM boundary to reach elements inside the Shadow Tree.
In the earlier TypeScript example, the reason Tailwind styles do not apply to <div class="container mx-auto p-4 bg-blue-200">
is because the corresponding CSS rules exist outside the Shadow DOM, in the global scope of the main document. The Shadow DOM blocks those rules from being applied inside the component.
A Note on Tailwind CSS v4: Tailwind CSS v4 boasts better performance thanks to a new engine. However, its core styling mechanism remains the same: it scans project files and generates global CSS based on the utility classes used. As a result, even with v4, the incompatibility with Shadow DOM remains unresolved.
Is There Any Way Around This?
While researching ways to solve this issue, I did come across a few possible workarounds. However, all of them either compromised the strengths of Web Components or Tailwind CSS, or required a high implementation cost. In the end, I couldn't find any solution that truly solves the problem. Still, here are some workarounds, even if they feel like desperate measures.
Copy and Paste the Built Tailwind CSS into the Shadow DOM.
This method involves manually or using a build tool to extract the CSS rules corresponding to the Tailwind classes used in each Web Component and embedding them as a <style>
tag inside the component's Shadow DOM.
Disadvantages:
- Very time-consuming and difficult to maintain
- Leads to duplicated CSS for each component, increasing file size
- The advantages of Tailwind's JIT compiler, which only generates the styles you use, cannot be fully utilized.
- Deviates from the Tailwind operational workflow, including config files and plugins
Don't Use Shadow DOM
This approach places elements in the Light DOM instead of using Shadow DOM in your Web Components. In this case, the elements are considered part of the main document's DOM tree, so global Tailwind styles are applied to them.
Disadvantages:
- The biggest benefit of Web Components, which is style encapsulation, is lost in this case. As a result, external CSS may affect the component, and the component's styles may leak out as well, compromising the independence of the component.
As you can see from these approaches, it is clear that the strong encapsulation provided by Shadow DOM and Tailwind CSS's reliance on global style sheets are basically different in design philosophy. Trying to combine the two tends to undermine the benefits of one or the other.
Conclusion: Web Components and Tailwind CSS Should Not Be Used Together
As we've seen, combining Web Components, especially when using Shadow DOM, with Tailwind CSS should generally be avoided since the advantages of each technology tend to cancel each other out.
This is because the two technologies have fundamentally different approaches to styling, which end up clashing with each other.
- Web Components (Shadow DOM) are designed to completely encapsulate component styles and isolate them from the outside.
- Tailwind CSS, on the other hand, generates CSS corresponding to utility classes as a global stylesheet, assuming it will be applied across the entire page.
Because of this, the convenient utility class styles generated by Tailwind cannot cross the strict boundaries of the Shadow DOM and therefore do not apply inside the component.
Although there are workarounds, they often sacrifice component independence and increase development complexity. In many cases, these solutions become counterproductive. To maximize the benefits of each technology, it would be wise to avoid this combination.
I hope this article serves as a useful reference for anyone considering combining Web Components and Tailwind CSS.
関連記事 | Related Posts
We are hiring!
【UI/UXエンジニア(フロントエンド)】DX開発G/大阪
DX開発グループについて全国約4,000店舗のトヨタ販売店の営業プロセスを中心に、販売店スタッフのお困りごとをテクノロジーとクリエイティブの力で解決に導く事業を展開しています。募集背景当グループでは、全国約4,000店舗のトヨタ販売店の営業プロセスを中心に、販売店スタッフのお困りごとをテクノロジーとクリエイティブの力で解決に導く「販売店DX事業」を展開しています。
【UI/UXデザイナー】クリエイティブ室/東京・大阪
クリエイティブ室についてKINTOやトヨタが抱えている課題やサービスの状況に応じて、色々なプロジェクトが発生しそれにクリエイティブ力で応えるグループです。所属しているメンバーはそれぞれ異なる技術や経験を持っているので、クリエイティブの側面からサービスの改善案を出し、周りを巻き込みながらプロジェクトを進めています。