Meefik's Blog

Freedom and Open Source

Tailwind CSS v4 and the Shadow DOM

19 Mar 2025 | tailwindcss

Tailwind CSS v4 was recently released, and with it came a problem when using the Shadow DOM. You can find the issue here: tailwindlabs/tailwindcss#15005.

Tailwind v4 uses @property to define defaults for custom properties. Currently, shadow roots do not support @property. Although it was explicitly disallowed in the spec, there is ongoing discussion about adding support: w3c/css-houdini-drafts#1085.

It is unknown if the developers will fix this issue. In this post, we will consider workarounds to address it.

Workaround 1: Global @property Declarations with Vite

One straightforward approach is to declare your @property rules in the main document scope, effectively making them available globally. While this approach offers less encapsulation, it works because custom properties inherit down the DOM tree by default.

Tailwind doesn’t currently offer a built-in way to extract just the @property definitions. However, if you’re using Vite, you can implement a simple build-time transformation to achieve this. This solution is described here.

Workaround 2: Programmatic Property Application

Alternatively, you can dynamically apply the custom property values directly within your component’s Shadow DOM for more explicit control within the encapsulated scope.

You can use this code to add Tailwind properties to global style sheets:

import styles from './styles.css?inline';

const shadowSheet = new CSSStyleSheet();
shadowSheet.replaceSync(styles.replace(/:root/ug, ':host'));

const globalSheet = new CSSStyleSheet();
for (const rule of shadowSheet.cssRules) {
  if (rule instanceof CSSPropertyRule) {
    globalSheet.insertRule(rule.cssText);
  }
}

document.adoptedStyleSheets.push(globalSheet);

export class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.adoptedStyleSheets = [shadowSheet];
    // ...
  }
}

Or you can replace @property with variables like this:

import styles from './styles.css?inline';

const shadowSheet = new CSSStyleSheet();
shadowSheet.replaceSync(styles.replace(/:root/ug, ':host'));

const properties = [];
for (const rule of shadowSheet.cssRules) {
  if (rule instanceof CSSPropertyRule) {
    if (rule.initialValue) {
      properties.push(`${rule.name}: ${rule.initialValue}`);
    }
  }
}
shadowSheet.insertRule(`:host { ${properties.join('; ')} }`);

export class MyComponent extends HTMLElement {
  constructor() {
    super();
    const shadowRoot = this.attachShadow({ mode: 'open' });
    shadowRoot.adoptedStyleSheets = [shadowSheet];
    // ...
  }
}

Comments