How to use Tailwind with shadow dom? #1935
-
This question is not only related to Tailwind, but to other similar frameworks/libraries. I just created my first HTML web component. I create a template and inside that I added Tailwind classes. The component is by design isolated from the rest, which often is a good thing. However, in my case I want it to inherit the CSS because of Tailwind. It probably does not matter, but I use it with NodeJS and Electron. Any ideas how to get around it? const template = document.createElement("template");
template.innerHTML = /*html */ `
<button class="p-4 bg-red-700">Button</button>
`;
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define("my-component", MyComponent);
module.exports = MyComponent; |
Beta Was this translation helpful? Give feedback.
Replies: 26 comments 44 replies
-
An old article but other articles point in this direction as well. https://www.smashingmagazine.com/2016/12/styling-web-components-using-a-shared-style-sheet/
|
Beta Was this translation helpful? Give feedback.
-
Hi @jenstornell - I'm actually facing the exact same issue. So your final solution was to "give up" on the usage af shadow DOM? |
Beta Was this translation helpful? Give feedback.
-
HI, any news here? I want to ad the tailwind to my custom components shadow. When i don't use the shadow I can not use the slots function. So I need it. Nope. Import in sytel is no option |
Beta Was this translation helpful? Give feedback.
-
This is how I fixed the problem.
|
Beta Was this translation helpful? Give feedback.
-
I want to share my workaround with all of you. Apart from Tailwind, I am using Swiper inside my component (which I am also importing with @import, I know, it's an antipattern, I have to solve that). Anyway, I'm creating the component with Angular v12. and I ended up doing:
I don't like all these steps, but you know what they say, if it works for now... I am thinking about automating this process somehow. For example, a CLI tool that generates a CSS file from HTML files with a bunch of |
Beta Was this translation helpful? Give feedback.
-
My solution was to use Twind at development and build time. The requirement is to have the HTML of the component available as a string. You can do this: import { create } from 'twind'
import { shim, virtualSheet, getStyleTag } from 'twind/shim/server'
const sheet = virtualSheet()
const { tw } = create({ sheet, preflight: false })
function getTailwindStyles(html) {
sheet.reset()
shim(html, { tw })
return getStyleTag(sheet)
} It will read the classes within your HTML, and generate a
That was the core idea. I am using Vite, and I have my components' templates in I actually created a Vite plugin, and when importing the import template from './my-web-component.html?shadow' This is the full plugin code: /*
* Prepend style tag with Tailwind styles into HTML templates
*
* https://twind.dev/api/modules/twind_shim_server.html
*/
import { create } from 'twind'
import { shim, virtualSheet, getStyleTag } from 'twind/shim/server'
const sheet = virtualSheet()
const { tw } = create({ sheet, preflight: false })
const fileRegex = /\.html\?shadow$/
export default function twindPlugin() {
return {
name: 'twind-plugin',
transform(src, id) {
if (fileRegex.test(id)) {
sheet.reset()
const template = shim(src, { tw })
const styles = getStyleTag(sheet)
return {
code: `export default ${JSON.stringify(styles + template)}`,
map: { mappings: '' },
}
}
},
}
} To use it, just add the plugin in Vite configuration: import twindPlugin from './vite-twind-plugin'
export default {
plugins: [
twindPlugin(),
],
} |
Beta Was this translation helpful? Give feedback.
-
Current Chrome and Firefox have already supported linking a stylesheet in the shadow DOM. See example: https://jsfiddle.net/straker/vj6vc7pn/ |
Beta Was this translation helpful? Give feedback.
-
Use the css selector const template = document.createElement("template");
template.innerHTML = /*html */ `
<button part="mybutton">Button</button>
`;
class MyComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define("my-component", MyComponent);
const style = document.createElement('style');
style.textContent = `
my-component::part(mybutton) {
@apply p-4 bg-red-700;
}
`;
document.documentElement.appendChild(style);
module.exports = MyComponent; |
Beta Was this translation helpful? Give feedback.
-
i'd like to share an approach i've been using when building Web Components with StencilJS. CAVEAT: most of my projects only have a single top-level component that's built from nested functional components. Within the Stencil component definition, you can specify a
I'm using the Tailwind CLI to generate the
The INPUT file passed to the CLI defines the Tailwind references and any standard CSS that's needed for the project:
You can see how this all fits together in this sample repo: https://github.com/eswat2/proto-tinker-wc I've used this same approach to build all of my Stencil+Tailwind projects: https://wc-tinker.vercel.app Again, this approach may not be useful in all cases, but i've found it to be a very useful pattern for my projects... |
Beta Was this translation helpful? Give feedback.
-
Another approach for usage with @tailwind base;
:host {
display: block;
}
h1 {
color: blue;
& p {
color: green;
}
} import componentStyles from './my-element.css'
// within your element ...
static styles = [
css`${unsafeCSS(componentStyles)}`
] Although the styles are generated correctly, the HMR for lit and vite isn't quite there yet. Also can be used without wrapping it in import componentStyles from './my-element.css'
// within your element ...
static styles = [componentStyles]
// or
static styles = componentStyles |
Beta Was this translation helpful? Give feedback.
-
Solution:
directive.ts import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appShadowDomTailwind]'
})
export class ShadowDomTailwindDirective {
constructor(el: ElementRef<HTMLElement>) {
// Important to avoid FOUC
el.nativeElement.hidden = true;
const div = document.createElement('link');
div.rel = "stylesheet";
div.href = environment.production ? "https://dieringe-web-components.web.app/tailwind.css" : "tailwind.css";
div.onload = () => {
el.nativeElement.hidden = false;
}
el.nativeElement.appendChild(div);
}
} angular.json "styles": [
"src/styles.scss",
{
"input": "src/tailwind.css",
"bundleName": "tailwind"
}
], Usage<!-- Put this anywhere in the shadow dom component -->
<div appShadowDomTailwind></div> Theoretically, this has no performance/bundle size impact and it seems to work, even though it's ugly af... |
Beta Was this translation helpful? Give feedback.
-
I think constructible stylesheets will be the answer to this if they are eventually widely adopted by browsers (and frameworks). In the meantime, adding a Alternatively, if anyone is using Vue 3 web components with |
Beta Was this translation helpful? Give feedback.
-
It is implemented by using the unsafe and adoptstyles of lit, based on the constructive stylesheet. Look here |
Beta Was this translation helpful? Give feedback.
-
I was able to add Tailwind styles in shadow dom using Twind and wrote a small guide: https://gourav.io/blog/tailwind-in-shadow-dom |
Beta Was this translation helpful? Give feedback.
-
Somehow the new version of twind (https://twind.style) is hard to find on Google, but in their docs I found a pretty good solution: https://twind.style/with-web-components import install from '@twind/with-web-components'
import config from './twind.config'
const withTwind = install(config)
class TwindElement extends withTwind(HTMLElement) {
constructor() {
super()
const shadow = this.attachShadow({ mode: 'open' })
shadow.innerHTML = `<h1 class="text-3xl font-bold underline">Hello world!</h1>`
}
}
customElements.define('twind-element', TwindElement) |
Beta Was this translation helpful? Give feedback.
-
Just dropping my two cents here. This seems to work in my use case:
In the parent document, I have this stylesheet:
I'm using Laravel with Mix at the moment and this ensures I will always get the correct app.css stylesheet filename, even when it's hashed in production. I get the element, copy it, and insert it into the ShadowDOM. For bonus point I could dedicate a Does anyone have arguments on why I shouldn't use this method? I'm not building any component library that should be used across applications (for now 😉), it's just a workaround to get TailwindCSS styles into my web application's webcomponents. |
Beta Was this translation helpful? Give feedback.
-
This was my solution:When I import index.css it loads the tailwind as a <style> html element. Then I adopt that style tag into my shadow dom. index.css@tailwind base;
@tailwind components;
@tailwind utilities; main.tsximport React from 'react'
import ReactDOM from 'react-dom/client'
import { Bootstrap } from './Bootstrap';
import './index.css';
const styleTags = document.getElementsByTagName('style');
const root = (
document
.getElementById('root')
.attachShadow({ mode: 'open' })
);
if (styleTags.length !== 0) {
const lastStyleTag = styleTags[styleTags.length - 1];
root.appendChild(lastStyleTag);
}
ReactDOM.createRoot(root).render(
<React.StrictMode>
<Bootstrap />
</React.StrictMode>
); |
Beta Was this translation helpful? Give feedback.
-
Using Tailwind CSS with Shadow DOM involves some considerations. First, ensure that your Shadow DOM is encapsulated within the desired component. To apply Tailwind styles, include the required utility classes within the component's style tag. However, keep in mind that some dynamic features of Tailwind may not work seamlessly within Shadow DOM due to encapsulation. To address this, you can manually extract and include relevant styles from Tailwind's output in your Shadow DOM. This allows you to leverage Tailwind's utility classes while respecting the encapsulation provided by Shadow DOM, providing a balance between utility and encapsulation. To know more, read here |
Beta Was this translation helpful? Give feedback.
-
So, tailwind can't support shadow DOM out of the box? |
Beta Was this translation helpful? Give feedback.
-
Does somebody have a working example of vite+react? Can't manage to get it work. |
Beta Was this translation helpful? Give feedback.
-
For Vue using Vite index.ts import install from '@twind/with-web-components';
import { defineCustomElement } from 'vue';
import MyComponent from './my.ce.vue';
import config from './twind.config';
const withTwind = install(config);
customElements.define(
'your-component',
withTwind(defineCustomElement(MyComponent)),
); twind.config.js import { defineConfig } from '@twind/core';
import presetAutoprefix from '@twind/preset-autoprefix';
import presetTailwind from '@twind/preset-tailwind';
export default defineConfig({
presets: [presetAutoprefix(), presetTailwind()],
hash: false,
}); |
Beta Was this translation helpful? Give feedback.
-
The simplest solution is to define an element as the root inside the shadow DOM with an ID, and then prepend this ID before each Tailwind class. You can achieve this using the postcss-prefix-selector package. |
Beta Was this translation helpful? Give feedback.
-
@rezasohrabi thank you so much for the repo; got a lot of ideas from it. I opened an issue to ask a question; I'd appreciate your response in there. |
Beta Was this translation helpful? Give feedback.
-
Hello everyone, I'm building browser extension with this setup. I'm using webpack and webpack-server to build a extension. package.json "dependencies": {
"@thedutchcoder/postcss-rem-to-px": "^0.0.2",
"css-loader": "^7.1.2",
"postcss-loader": "^8.1.1",
"vue": "^3.4.21",
"vue-router": "^4.3.2",
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^12.0.2",
"fs-extra": "^11.2.0",
"html-webpack-plugin": "^5.6.0",
"mini-css-extract-plugin": "^2.9.0",
"postcss": "^8.4.38",
"source-map-loader": "^5.0.0",
"tailwindcss": "^3.4.3",
"vite": "^5.2.8",
"vue-loader": "^17.4.2",
"webpack": "^5.91.0",
"webpack-dev-server": "^5.0.4"
}
postcss.config.js module.exports = {
plugins: [
require('tailwindcss'),
require('@thedutchcoder/postcss-rem-to-px'),
require('autoprefixer')
],
}; tailwind.config.js /** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx,vue}'],
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
mono: ['Source Code Pro', 'monospace'],
},
},
},
plugins: [],
} App.vue (content script) <div
class="text-white fixed bottom-0 right-0 bg-zinc-800 h-[500px] m-6 p-4 rounded-2xl w-[350px] drop-shadow-xl"
style="z-index: 999999999999"
>
.
. index.js (content.js) // Create a shadow root
const host = document.createElement("div");
host.id = "main-palette";
const shadow = host.attachShadow({ mode: "open" });
// Create a root element inside the shadow root
const appRoot = document.createElement('div');
appRoot.setAttribute('id', 'app');
createApp(App)
.mount(appRoot);
// Setup the style
host.style.all = "initial"; // Reset all inherited styles
// Fetch and apply Tailwind CSS
const response = await fetch(browser.runtime.getURL('/contentScript.css')); // your CSS
const mainCSS = await response.text();
const sheet = new CSSStyleSheet();
sheet.replaceSync(mainCSS);
shadow.adoptedStyleSheets = [sheet];
shadow.appendChild(appRoot);
document.body.appendChild(host); |
Beta Was this translation helpful? Give feedback.
-
Far from a perfect solution but I think with some tinkering you could solve this with adopted stylesheets. |
Beta Was this translation helpful? Give feedback.
-
For anyone interested it works using with Vite, import { render } from 'solid-js/web'
import styles from './app.css?inline'
import { App } from './app'
const host = document.createElement('my-app')
const shadow = host.attachShadow({ mode: 'open' })
const sheet = new CSSStyleSheet()
sheet.replaceSync(styles)
shadow.adoptedStyleSheets = [sheet]
document.body.appendChild(host)
render(() => <App />, shadow) @tailwind base;
@tailwind components;
@tailwind utilities;
/** ... */ Note: if you're using - :root {
+ :root,
+ :host {
/** ... */
} |
Beta Was this translation helpful? Give feedback.
An old article but other articles point in this direction as well.
https://www.smashingmagazine.com/2016/12/styling-web-components-using-a-shared-style-sheet/