Let AI write emails and messages for you 🔥

Add Tailwind styles to shadow DOM

Gourav Goyal

Gourav Goyal

Dec 15, 2022

The problem with Tailwind

Adding Tailwind classes to shadow DOM elements won’t directly work even though Tailwind is set up for your project. The definition of Tailwind classes that you applied is present in a stylesheet, but that stylesheet is not accessible inside shadow DOM. Read more about shadow DOM in my other article.

However, there’s a workaround using the Twind library.

Twind is a small compiler that converts Tailwind utility classes into CSS at runtime.

  • No build step required.
  • Twind ships the compiler, not the CSS. This means unlimited styles and variants for one low fixed cost of ~17kB.
  • Framework agnostic.
  • Support for Tailwind v3 with Tailwind preflight enabled by default.
  • Twind claims to be faster than most CSS-in-JS libraries.
  • Escape hatch for arbitrary CSS

Let’s do this!

Install Twind

# install core package
npm install @twind/core 

# add tailwind support and autoprefix plugin
npm install @twind/preset-autoprefix @twind/preset-tailwind

Install construct-style-sheets-polyfill

Our solution will use Constructable Stylesheet Objects and shadowRoot.adoptedStyleSheets but it has a limited browser support. It won’t work on Safari and old versions of Firefox etc. at the moment (December 2022). So we will be using Constructible style sheets polyfill, which offers a solution for all modern browsers and IE 11. No changes will occur in a browser that supports this feature by default.

Install construct-style-sheets-polyfill:

npm i construct-style-sheets-polyfill

Configure Twind

Twind config file

create twind.config.js file at the root of your project and paste the below code for Tailwind and autoprefix plugin support:

import { defineConfig } from "@twind/core";
import presetTailwind from "@twind/preset-tailwind";
import presetAutoprefix from "@twind/preset-autoprefix";

// use px instead of rem (Tailwind's default unit) so that the size of shadow dom elements is not affected by html base font size https://github.com/tw-in-js/twind/issues/437#issue-1532077112
const presetRemToPx = ({ baseValue = 16 } = {}) => {
  return {
    finalize(rule) {
      return {
        ...rule,
        // d: the CSS declaration body
        // Based on https://github.com/TheDutchCoder/postcss-rem-to-px/blob/main/index.js
        d: rule.d.replace(
          /"[^"]+"|'[^']+'|url\([^)]+\)|(-?\d*\.?\d+)rem/g,
          (match, p1) => {
            if (p1 === undefined) return match;
            return `${p1 * baseValue}${p1 == 0 ? "" : "px"}`;
          }
        )
      };
    }
  };
};

export default defineConfig({
  presets: [presetAutoprefix(), presetTailwind(/* options */), presetRemToPx()]
  /* config */
});

You can add Tailwind plugins as well https://twind.style/presets#official-presets

Configuration for Typescript

if you’re using Typescript, open tsconfig.json and mention module as CommonJS to make imports work correctly:

{
  "compilerOptions": {
    "module": "CommonJS"
  }
}

Use Twind

In your js/ts file, create a custom stylesheet, create its Twind instance, link the sheet target to the shadow root, and tell Twind to observe for Tailwind classes:

import { twind, cssom, observe, install } from "@twind/core";
// support shadowroot.adoptedStyleSheets in all browsers
import "construct-style-sheets-polyfill";
// mention right path for twind.config.js
import config from "./twind.config";


// Create separate CSSStyleSheet
const sheet = cssom(new CSSStyleSheet());

// Use sheet and config to create an twind instance. `tw` will
// append the right CSS to our custom stylesheet.
const tw = twind(config, sheet);

// get hold of the shadow dom root
const shadowRoot = document.querySelector(
  ".my-shadow-root-container"
).shadowRoot;

// link sheet target to shadow dom root
shadowRoot.adoptedStyleSheets = [sheet.target];

// finally, observe using tw function
observe(tw, shadowroot);

That’s it; just write your friendly neighborhood Tailwind classes, and Twind (tw) will link its corresponding style to that custom stylesheet.

<div className="text-gray-700 bg-slate-50">Title</div>
That's all, folks!

Gourav Goyal

Gourav Goyal