本文へスキップ / Skip to main content
KINTO Tech Blog

7 min read

Frontend

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

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

This summary was generated by AI. Please refer to the article for accurate details.

Read the summary

Web Components and Tailwind CSS don’t work well together—especially when using Shadow DOM. The core issue is that Shadow DOM isolates styles inside a component, while Tailwind generates global CSS that applies everywhere. This creates a clash because Tailwind’s styles can’t reach elements inside the Shadow Tree. The problem starts with how Shadow DOM works. When a component uses Shadow DOM, its internal DOM and styles are sealed off from the rest of the page. Styles from outside—like those from Tailwind—can’t apply to elements inside that shadow tree. Even if you use Tailwind classes like flex or text-blue-500 inside a component, the generated CSS rules stay in the global scope and don’t cross into the shadow tree. Tailwind CSS v4 improves performance with a new engine, but it doesn’t fix this issue. The styling mechanism remains the same: it scans all files and creates one global stylesheet. That means the generated CSS is always global, and it can’t penetrate the Shadow DOM boundary. So even with v4, the incompatibility stays. There are workarounds, but they come with big trade-offs. One option is copying Tailwind’s generated CSS into each component’s Shadow DOM. But that’s messy—time-consuming, hard to maintain, and it duplicates styles. It also breaks Tailwind’s JIT compiler and workflow. Another option is skipping Shadow DOM entirely and using the Light DOM. But then the component loses its style isolation, and external styles can interfere with it. In short, combining Web Components with Tailwind CSS is generally not recommended. The two technologies have opposite design goals: one isolates styles, the other assumes global styling. Because of this, they clash. Any solution either sacrifices encapsulation or adds complexity. To get the best of both, it’s better to avoid using them 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.

Facebook

Follow Us

XConnpassWantedly

KINTOテクノロジーズの最新情報をSNSで発信中!イベント・テックブログ更新情報もお届けします。