ui icon indicating copy to clipboard operation
ui copied to clipboard

Styles are partially missing in a custom element environment

Open posva opened this issue 9 months ago • 1 comments

Environment

  • Operating System: Darwin
  • Node Version: v22.11.0
  • Nuxt Version: -
  • CLI Version: 3.24.1
  • Nitro Version: -
  • Package Manager: [email protected]
  • Builder: -
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Is this bug related to Nuxt or Vue?

Vue

Version

3.x

Reproduction

  • https://github.com/posva/starter-vue

Description

Styles applied to body and other styles applied to .light (html element) cannot be inherited in a shadow dom context.

You will notice that the starter doesn't look like the original one (no dark mode, no radius on buttons) and overall a lot of variables and styles missing

Image

Additional context

  • the styles must be added to the <style> tag of the custom element for them to be bundled with the custom element

  • I wanted to use Nuxt UI to build the Pinia Colada Devtools but I couldn't patch all of the missing styles so I boiled it down to just the starter. In the context of a Custom Element there is no body, so it might be useful to either allow customizing the selector these classes are applied to or require an a specific id to be used within the Shadow DOM

  • it probably requires using the :host selector

  • TailwindCSS also creates properties and those do not work within shadow dom. So the CE needs to extract them and attach them to the document. This is what I added in Pinia Colada devtools to make it work:

    function extractCssPropertyRules(styleSheets: StyleSheetList) {
      return [...styleSheets]
        .flatMap((s) => [...s.cssRules])
        .filter((rule) => rule instanceof CSSPropertyRule)
    }
    
    function attachCssPropertyRules() {
      const devtools = devtoolsEl.value
      if (!devtools || !devtools.shadowRoot) {
        throw new Error('No devtools elemnt found for Pinia Colada devtools')
      }
    
      const el = devtoolsEl.value
    
      if (!el || !el.shadowRoot || document.getElementById('__pc-tw-properties')) return
    
      const cssPropertyRules = extractCssPropertyRules(el.shadowRoot.styleSheets)
      const cssPropertyRulesText = cssPropertyRules.map((rule) => rule.cssText).join('')
      const style = document.createElement('style')
      style.setAttribute('id', '__pc-tw-properties')
      style.textContent = cssPropertyRulesText
      document.head.appendChild(style)
    }
    

Logs


posva avatar Apr 09 '25 13:04 posva

I managed to partially fix this with a patch that adds :host to some selectors but the color mode is broken as some components relies on the html tag and others not but it should only be applied within the shadow root. You can try applying the patch below and you will see that the theme is not properly applied. I tried disabling colorMode and recreating my own but that breaks other components like USelect.

Here is the patch:

Create file patches/@nuxt__ui.patch, pnpm applies it after installing dips.

diff --git a/dist/runtime/index.css b/dist/runtime/index.css
index 18691543a248558fed6e14bc78c0a16e0d522f9d..610133ed271cceb132ac355f7b5f44982f99c2ca 100644
--- a/dist/runtime/index.css
+++ b/dist/runtime/index.css
@@ -1 +1 @@
-@import "#build/ui.css";@import "./keyframes.css";@variant light (&:where(.light, .light *));@variant dark (&:where(.dark, .dark *));@layer base{body{@apply antialiased text-(--ui-text) bg-(--ui-bg) scheme-light dark:scheme-dark}.light,:root{--ui-text-dimmed:var(--ui-color-neutral-400);--ui-text-muted:var(--ui-color-neutral-500);--ui-text-toned:var(--ui-color-neutral-600);--ui-text:var(--ui-color-neutral-700);--ui-text-highlighted:var(--ui-color-neutral-900);--ui-bg:var(--color-white);--ui-bg-muted:var(--ui-color-neutral-50);--ui-bg-elevated:var(--ui-color-neutral-100);--ui-bg-accented:var(--ui-color-neutral-200);--ui-bg-inverted:var(--ui-color-neutral-900);--ui-border:var(--ui-color-neutral-200);--ui-border-muted:var(--ui-color-neutral-200);--ui-border-accented:var(--ui-color-neutral-300);--ui-border-inverted:var(--ui-color-neutral-900);--ui-radius:var(--radius-sm);--ui-container:var(--container-7xl)}.dark{--ui-text-dimmed:var(--ui-color-neutral-500);--ui-text-muted:var(--ui-color-neutral-400);--ui-text-toned:var(--ui-color-neutral-300);--ui-text:var(--ui-color-neutral-200);--ui-text-highlighted:var(--color-white);--ui-bg:var(--ui-color-neutral-900);--ui-bg-muted:var(--ui-color-neutral-800);--ui-bg-elevated:var(--ui-color-neutral-800);--ui-bg-accented:var(--ui-color-neutral-700);--ui-bg-inverted:var(--color-white);--ui-border:var(--ui-color-neutral-800);--ui-border-muted:var(--ui-color-neutral-700);--ui-border-accented:var(--ui-color-neutral-700);--ui-border-inverted:var(--color-white)}}
\ No newline at end of file
+@import "#build/ui.css";@import "./keyframes.css";@variant light (&:where(.light, .light *));@variant dark (&:where(.dark, .dark *));@layer base{body{@apply antialiased text-(--ui-text) bg-(--ui-bg) scheme-light dark:scheme-dark}.light,:root,:host{--ui-text-dimmed:var(--ui-color-neutral-400);--ui-text-muted:var(--ui-color-neutral-500);--ui-text-toned:var(--ui-color-neutral-600);--ui-text:var(--ui-color-neutral-700);--ui-text-highlighted:var(--ui-color-neutral-900);--ui-bg:var(--color-white);--ui-bg-muted:var(--ui-color-neutral-50);--ui-bg-elevated:var(--ui-color-neutral-100);--ui-bg-accented:var(--ui-color-neutral-200);--ui-bg-inverted:var(--ui-color-neutral-900);--ui-border:var(--ui-color-neutral-200);--ui-border-muted:var(--ui-color-neutral-200);--ui-border-accented:var(--ui-color-neutral-300);--ui-border-inverted:var(--ui-color-neutral-900);--ui-radius:var(--radius-sm);--ui-container:var(--container-7xl)}.dark{--ui-text-dimmed:var(--ui-color-neutral-500);--ui-text-muted:var(--ui-color-neutral-400);--ui-text-toned:var(--ui-color-neutral-300);--ui-text:var(--ui-color-neutral-200);--ui-text-highlighted:var(--color-white);--ui-bg:var(--ui-color-neutral-900);--ui-bg-muted:var(--ui-color-neutral-800);--ui-bg-elevated:var(--ui-color-neutral-800);--ui-bg-accented:var(--ui-color-neutral-700);--ui-bg-inverted:var(--color-white);--ui-border:var(--ui-color-neutral-800);--ui-border-muted:var(--ui-color-neutral-700);--ui-border-accented:var(--ui-color-neutral-700);--ui-border-inverted:var(--color-white)}}
diff --git a/dist/runtime/plugins/colors.js b/dist/runtime/plugins/colors.js
index 74ae833e56869cf76c0d8077f7082f2cd02c940b..ebc7f704498ed222320d4bc4a1800109255a4d67 100644
--- a/dist/runtime/plugins/colors.js
+++ b/dist/runtime/plugins/colors.js
@@ -20,10 +20,10 @@ export default defineNuxtPlugin(() => {
   const root = computed(() => {
     const { neutral, ...colors2 } = appConfig.ui.colors;
     return `@layer base {
-  :root {
+  :root, :host {
   ${Object.entries(appConfig.ui.colors).map(([key, value]) => generateShades(key, value)).join("\n  ")}
   }
-  :root, .light {
+  :root, :host, .light {
   ${Object.keys(colors2).map((key) => generateColor(key, 500)).join("\n  ")}
   }
   .dark {

posva avatar Apr 10 '25 08:04 posva