| |

Tailwind CSS 65 🎨 Styling Web Components and Shadow DOM Encapsulation Boundaries

A Web Component is a custom element with its own encapsulated DOM. The shadow root is the boundary: the styles inside do not leak out, and the styles outside do not leak in. That encapsulation is what makes a component reusable across applications, and it is also what makes Tailwind’s global stylesheet invisible to the component. The utilities do not apply inside the shadow root unless they are injected there. The design tokens do not cross the boundary unless they are passed as CSS custom properties. This chapter covers the mechanisms for getting Tailwind into a shadow root, the strategies for keeping the design tokens in sync, and the patterns that make the encapsulation an asset rather than an obstacle.

Key point: A CSS custom property is the only thing that inherits through the shadow boundary. A global stylesheet is not inherited, a utility class is not inherited, but a --color-primary value on the host element is visible inside the shadow root. The custom properties are the bridge between the application’s Tailwind theme and the component’s encapsulated styles.


Why shadow DOM and Tailwind need a strategy

The encapsulation problem. A custom element’s shadow root has its own style scope. The global Tailwind stylesheet, loaded in the document, does not apply inside the shadow root. A <button class="bg-blue-600"> inside the shadow root has no background because the bg-blue-600 utility is not part of the shadow root’s styles.

The utility problem. The Tailwind approach depends on a large generated stylesheet. The shadow root needs its own copy of the relevant utilities, or a different approach entirely. The choices are to inject the stylesheet, to use the constructable stylesheets, or to use the CSS custom properties.

The token problem. The design tokens are the shared vocabulary. The --color-primary and the --radius-md are the values the component uses. The custom properties inherit through the shadow boundary, so a --color-primary on the host element is visible inside the shadow root. The component’s CSS uses var(--color-primary), and the value comes from the application’s theme.

The bundle problem. A component that ships its own Tailwind stylesheet duplicates the utilities. A component that shares the application’s stylesheet cannot, because the shadow root does not see it. The constructable stylesheets are the modern mechanism for sharing the adopted stylesheet across the many components.

The performance problem. The shadow DOM does not change the CSS performance model. The styles inside the shadow root are scoped, and the browser does not match the selectors across the boundary. The encapsulation is a specificity and a matching win.

The browser support problem. The shadow DOM is supported in every modern browser. The constructable stylesheets are supported in the Chromium and the Safari browsers, and the Firefox support arrived in version 122. The adoptedStyleSheets property is the modern API.

The framework problem. The Lit library is the common framework for the web components. The Lit’s static styles property is the mechanism for the component’s CSS, and the Tailwind integration is the build step that produces the CSS the component adopts.


a. The shadow DOM and the style scope

The shadow DOM creates a separate style scope. The styles inside the shadow root apply only to the elements inside, and the document styles do not apply inside unless they are inherited.

The basic shadow root

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        button {
          background: var(--color-primary, blue);
          color: var(--color-primary-foreground, white);
          padding: 0.5rem 1rem;
          border-radius: var(--radius-md, 0.375rem);
        }
      </style>
      <button><slot></slot></button>
    `;
  }
}
customElements.define('my-button', MyButton);

The <style> element inside the shadow root is scoped. The button selector matches only the shadow root’s button.

The document styles

The document’s styles do not apply inside the shadow root. The Tailwind utilities in the document are invisible to the shadow root’s elements.

<my-button class="bg-blue-600">Click</my-button>

The bg-blue-600 is on the host element, and the host element’s class is in the document scope. The shadow root’s button does not inherit the background.

The inherited properties

The inherited CSS properties cross the shadow boundary. The color, the font-family, the font-size, and the custom properties are inherited from the host element.

/* The host element */
my-button {
  color: var(--color-foreground);
  font-family: var(--font-sans);
}

The color and the font-family inherit into the shadow root, and the button inside uses them.

The :host selector

The :host selector targets the host element from inside the shadow root.

:host {
  display: inline-block;
}

:host([disabled]) {
  opacity: 0.5;
  pointer-events: none;
}

The :host([disabled]) is the state selector. The host element’s attribute is the signal.

The ::slotted() selector

The ::slotted() selector targets the light DOM children from inside the shadow root.

::slotted(span) {
  font-weight: bold;
}

The ::slotted(span) matches the <span> elements the user passed as the slot content. The selector has the limits: it matches only the direct children, and it cannot match the descendants.

The scope table

ScopeApplies to
DocumentThe light DOM
Shadow rootThe shadow DOM
:hostThe host element
::slotted()The slotted light DOM children
InheritedThe color, the font, the custom properties

b. The Tailwind integration strategies

The strategies for getting the Tailwind utilities into the shadow root. Each has the trade-offs.

The injected stylesheet

The component fetches the Tailwind CSS and injects it into the shadow root.

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

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>${styles}</style>
      <button class="bg-blue-600 px-4 py-2 rounded">
        <slot></slot>
      </button>
    `;
  }
}

The ?inline import produces the CSS as a string. The string is interpolated into the <style> element. The approach works, but the full Tailwind stylesheet is injected into every instance.

The constructable stylesheet

The CSSStyleSheet constructor and the adoptedStyleSheets property share the stylesheet across the many components.

const sheet = new CSSStyleSheet();
sheet.replaceSync(`
  .btn { padding: 0.5rem 1rem; border-radius: var(--radius-md); }
  .btn-primary { background: var(--color-primary); color: var(--color-primary-foreground); }
`);

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.adoptedStyleSheets = [sheet];
    this.shadowRoot.innerHTML = `
      <button class="btn btn-primary"><slot></slot></button>
    `;
  }
}

The sheet is created once, and every instance adopts it. The browser parses the CSS once, and the memory is shared. The approach is the modern recommendation.

The CSS custom properties

The custom properties inherit through the shadow boundary, so the component uses them in its own CSS.

/* The application's global CSS */
:root {
  --color-primary: 221 83% 53%;
  --color-primary-foreground: 0 0% 98%;
  --radius-md: 0.375rem;
}
class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        button {
          background: hsl(var(--color-primary));
          color: hsl(var(--color-primary-foreground));
          border-radius: var(--radius-md);
        }
      </style>
      <button><slot></slot></button>
    `;
  }
}

The component’s CSS uses the custom properties. The application’s theme provides the values, and the host element inherits them.

The Lit integration

The Lit library is the common framework. The static styles property is the mechanism.

import { LitElement, css, html } from 'lit';

class MyButton extends LitElement {
  static styles = css`
    :host {
      display: inline-block;
    }
    button {
      background: hsl(var(--color-primary));
      color: hsl(var(--color-primary-foreground));
      padding: 0.5rem 1rem;
      border-radius: var(--radius-md);
    }
  `;

  render() {
    return html`<button><slot></slot></button>`;
  }
}
customElements.define('my-button', MyButton);

The static styles is the component’s CSS. The Tailwind utilities are not used inside the Lit component directly; the custom properties are.

The Tailwind in Lit

The Tailwind can be used inside the Lit component with the unsafeCSS and the build step.

import { LitElement, html, unsafeCSS } from 'lit';
import tailwindStyles from './tailwind.css?inline';

class MyButton extends LitElement {
  static styles = unsafeCSS(tailwindStyles);

  render() {
    return html`<button class="bg-blue-600 px-4 py-2 rounded">...</button>`;
  }
}

The unsafeCSS wraps the Tailwind string, and the static styles applies it. The approach uses the full Tailwind inside the component.

The plugin approach

The @tailwindcss/postcss and the web-component-specific plugins generate the scoped styles. The plugin reads the component’s class names and produces the CSS.

The strategy table

StrategySharingBundle
Injected stylesheetNoFull Tailwind per instance
Constructable stylesheetYesOne parse
Custom propertiesYesThe component’s CSS only
Lit static stylesPer classThe component’s CSS
Tailwind in LitPer classFull Tailwind per class

c. The design tokens across the boundary

The design tokens are the shared vocabulary. The custom properties are the mechanism, and the host element is the bridge.

The token inheritance

The custom properties inherit through the shadow boundary from the host element.

:root {
  --color-primary: 221 83% 53%;
  --radius-md: 0.375rem;
}
<my-button class="[--color-primary:142_71%_45%]">Green button</my-button>

The [--color-primary:142_71%_45%] sets the custom property on the host element, and the shadow root’s button reads it. The component’s theme is overridden at the host.

The token naming convention

The token names are the contract between the application and the component. The --color-primary is the standard, and the component reads it.

/* The component's CSS */
button {
  background: hsl(var(--color-primary, 221 83% 53%));
}

The var(--color-primary, 221 83% 53%) is the fallback. The component works without the application’s theme, and the application’s theme overrides the fallback.

The dark mode

The dark mode is the set of the custom properties on the .dark class.

.dark {
  --color-primary: 0 0% 98%;
  --color-primary-foreground: 240 5.9% 10%;
}

The shadow root inherits the dark values when the host element is inside the .dark subtree. The component does not need a dark variant; the custom properties change, and the component’s CSS uses var(--color-primary).

The token documentation

The component’s README documents the tokens it reads. The --color-primary, the --radius-md, and the --font-sans are the contract.

## Theme tokens

- `--color-primary` — The primary background
- `--color-primary-foreground` — The primary text
- `--radius-md` — The border radius

The documentation is the API. The application knows which tokens to set.

The token fallback

The component’s CSS provides the fallback value for every token.

button {
  background: hsl(var(--color-primary, 221 83% 53%));
  border-radius: var(--radius-md, 0.375rem);
  font-family: var(--font-sans, system-ui, sans-serif);
}

The fallback makes the component work without the application’s theme. The application overrides the values.

The token table

TokenPurposeFallback
--color-primaryThe primary background221 83% 53%
--color-primary-foregroundThe primary text0 0% 98%
--radius-mdThe border radius0.375rem
--font-sansThe font familysystem-ui

Complete Example Session

// ============================================
// PART 1: THE BASIC SHADOW ROOT
// ============================================

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        button {
          background: hsl(var(--color-primary, 221 83% 53%));
          color: hsl(var(--color-primary-foreground, 0 0% 98%));
          padding: 0.5rem 1rem;
          border-radius: var(--radius-md, 0.375rem);
        }
      </style>
      <button><slot></slot></button>
    `;
  }
}
customElements.define('my-button', MyButton);

// ============================================
// PART 2: THE CONSTRUCTABLE STYLESHEET
// ============================================

const buttonStyles = new CSSStyleSheet();
buttonStyles.replaceSync(`
  button {
    background: hsl(var(--color-primary, 221 83% 53%));
    color: hsl(var(--color-primary-foreground, 0 0% 98%));
    padding: 0.5rem 1rem;
    border-radius: var(--radius-md, 0.375rem);
  }
`);

class MyButton extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.adoptedStyleSheets = [buttonStyles];
    this.shadowRoot.innerHTML = `<button><slot></slot></button>`;
  }
}

// ============================================
// PART 3: THE LIT COMPONENT
// ============================================

import { LitElement, css, html } from 'lit';

class MyButton extends LitElement {
  static styles = css`
    :host {
      display: inline-block;
    }
    button {
      background: hsl(var(--color-primary, 221 83% 53%));
      color: hsl(var(--color-primary-foreground, 0 0% 98%));
      padding: 0.5rem 1rem;
      border-radius: var(--radius-md, 0.375rem);
    }
  `;

  render() {
    return html`<button><slot></slot></button>`;
  }
}
customElements.define('my-button', MyButton);

// ============================================
// PART 4: THE TOKEN INHERITANCE
// ============================================

// The application's theme
:root {
  --color-primary: 221 83% 53%;
  --color-primary-foreground: 0 0% 98%;
  --radius-md: 0.375rem;
}

// The host element's override
<my-button class="[--color-primary:142_71%_45%]">Green</my-button>

// ============================================
// PART 5: THE DARK MODE
// ============================================

.dark {
  --color-primary: 0 0% 98%;
  --color-primary-foreground: 240 5.9% 10%;
}

// The component does not need a dark variant.
// The custom properties change, and the CSS reads the current value.

// ============================================
// PART 6: THE TAILWIND IN LIT
// ============================================

import { LitElement, html, unsafeCSS } from 'lit';
import tailwindStyles from './tailwind.css?inline';

class MyCard extends LitElement {
  static styles = unsafeCSS(tailwindStyles);

  render() {
    return html`
      <div class="rounded-lg bg-white p-6 shadow-md">
        <slot></slot>
      </div>
    `;
  }
}

// ============================================
// PART 7: THE SLOTTED CONTENT
// ============================================

class MyCard extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        ::slotted(h2) {
          font-size: 1.25rem;
          font-weight: 600;
        }
        ::slotted(p) {
          color: hsl(var(--color-muted-foreground));
        }
      </style>
      <div class="card">
        <slot name="header"></slot>
        <slot></slot>
      </div>
    `;
  }
}

// ============================================
// PART 8: THE HOST STATE
// ============================================

class MyButton extends HTMLElement {
  static observedAttributes = ['disabled'];

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === 'disabled') {
      this.shadowRoot.querySelector('button').disabled = newValue !== null;
    }
  }

  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.shadowRoot.innerHTML = `
      <style>
        :host([disabled]) {
          opacity: 0.5;
          pointer-events: none;
        }
      </style>
      <button><slot></slot></button>
    `;
  }
}

// ============================================
// PART 9: THE THEME TOKEN CONTRACT
// ============================================

/*
 * The component reads:
 *   --color-primary
 *   --color-primary-foreground
 *   --radius-md
 *   --font-sans
 *
 * The fallback values are provided in the CSS.
 * The application's theme overrides them.
 */

// ============================================
// PART 10: THE COMPLETE PATTERN
// ============================================

// 1. Use the shadow DOM for the encapsulation.
// 2. Use the custom properties for the tokens.
// 3. Use the constructable stylesheets for the sharing.
// 4. Use the adoptedStyleSheets for the many instances.
// 5. Use the Lit static styles for the framework.
// 6. Use the var() fallback for the standalone operation.
// 7. Use the :host for the host element.
// 8. Use the ::slotted() for the light DOM children.
// 9. Document the token contract.
// 10. The dark mode is the custom property change.

The ten parts showed the basic shadow root, the constructable stylesheet, the Lit component, the token inheritance, the dark mode, the Tailwind in Lit, the slotted content, the host state, the token contract, and the complete pattern.


Quick Reference

The Shadow DOM Selectors

SelectorTargets
:hostThe host element
:host([attr])The host with the attribute
::slotted(sel)The slotted children
:host-context(sel)The host in the context

The Integration Strategies

StrategySharingApproach
Injected stylesheetNoThe <style> element
Constructable stylesheetYesadoptedStyleSheets
Custom propertiesYesThe var() in the CSS
Lit static stylesPer classThe css template
Tailwind in LitPer classunsafeCSS

The Token Bridge

AspectMechanism
InheritanceThe custom properties
Host overrideThe inline style or the class
Dark modeThe .dark custom properties
FallbackThe var() second argument
DocumentationThe component README

The Inherited Properties

PropertyInherits
colorYes
font-familyYes
font-sizeYes
line-heightYes
Custom propertiesYes
backgroundNo
paddingNo
borderNo

The Browser Support

FeatureChromeFirefoxSafari
Shadow DOM53+63+10+
Constructable stylesheets73+122+16.4+
::slotted()53+63+10+
:host-context()53+63+10+

Best Practices

✅ Do This:

// Use the custom properties for the tokens.
button {
  background: hsl(var(--color-primary, 221 83% 53%));
}  // ✅

// Use the constructable stylesheets for the sharing.
const sheet = new CSSStyleSheet();
sheet.replaceSync('...');
this.shadowRoot.adoptedStyleSheets = [sheet];  // ✅

// Use the var() fallback.
background: hsl(var(--color-primary, 221 83% 53%));  // ✅

// Use the :host for the host element.
:host { display: inline-block; }  // ✅

// Use the ::slotted() for the light DOM.
::slotted(h2) { font-size: 1.25rem; }  // ✅

// Document the token contract.
// The README lists the tokens the component reads.  // ✅

// Use the Lit static styles for the framework.
static styles = css`...`;  // ✅

❌ Don’t Do This:

// Don't expect the document's Tailwind to apply.
<button class="bg-blue-600">  // ❌ Inside the shadow root, no effect

// Don't use the global CSS without the injection.
// The shadow root does not see the document stylesheet.  // ❌

// Don't use the same stylesheet without the sharing.
// The constructable stylesheet is the sharing mechanism.  // ❌

// Don't forget the var() fallback.
background: hsl(var(--color-primary));  // ❌ Breaks without the theme

// Don't target the shadow DOM from the document.
my-button button { ... }  // ❌ The shadow DOM is not reachable

// Don't rely on the ::slotted() descendants.
::slotted(div span) { ... }  // ❌ The selector only matches the direct children

// Don't skip the token documentation.
// The application does not know the contract.  // ❌

Common Pitfalls

PitfallWhy It HappensFix
Tailwind has no effectThe document stylesheetUse the custom properties or the injection
::slotted does not matchThe descendantsOnly the direct children
Token not foundThe host does not set itAdd the var() fallback
Style leakThe :host is not usedUse the scoped selectors
Dark mode does not applyThe custom properties do not changeSet the .dark values
Memory growsThe full Tailwind per instanceUse the constructable stylesheet
The state does not updateNo observedAttributesAdd the observed list

Real-World Examples

1. The Basic Shadow Root

this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `<style>...</style><button><slot></slot></button>`;

2. The Custom Property

button { background: hsl(var(--color-primary, 221 83% 53%)); }

3. The Constructable Stylesheet

const sheet = new CSSStyleSheet();
sheet.replaceSync('...');
this.shadowRoot.adoptedStyleSheets = [sheet];

4. The Lit Component

static styles = css`button { background: hsl(var(--color-primary)); }`;

5. The Host Override

<my-button class="[--color-primary:142_71%_45%]">Green</my-button>

6. The Dark Mode

.dark { --color-primary: 0 0% 98%; }

7. The Slotted

::slotted(h2) { font-size: 1.25rem; }

8. The Host State

:host([disabled]) { opacity: 0.5; }

9. The Token Contract

- `--color-primary` — The primary background

10. The Complete Pattern

this.shadowRoot.adoptedStyleSheets = [sheet];
this.shadowRoot.innerHTML = `<button><slot></slot></button>`;

Visual

The Shadow Boundary

┌────────────────────────────────────────────────────────────┐
│  THE SHADOW BOUNDARY                                       │
│                                                            │
│  DOCUMENT                                                  │
│  ┌──────────────────────────────────────────────────┐     │
│  │  <my-button class="[--color-primary:...]">        │     │
│  │    │                                              │     │
│  │    │  THE SHADOW BOUNDARY                         │     │
│  │    │  ┌────────────────────────────────────┐      │     │
│  │    │  │  #shadow-root                       │      │     │
│  │    │  │    <style>                          │      │     │
│  │    │  │      button { background: var(--color-primary) }│
│  │    │  │    </style>                          │      │     │
│  │    │  │    <button>                          │      │     │
│  │    │  │      <slot>                          │      │     │
│  │    │  │        (the light DOM children)      │      │     │
│  │    │  │      </slot>                         │      │     │
│  │    │  │    </button>                         │      │     │
│  │    │  └────────────────────────────────────┘      │     │
│  │    └──────────────────────────────────────────┘     │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The custom properties inherit through the boundary.       │
│  The document styles do not.                               │
└────────────────────────────────────────────────────────────┘

The Token Bridge

┌────────────────────────────────────────────────────────────┐
│  THE TOKEN BRIDGE                                          │
│                                                            │
│  APPLICATION                                               │
│  ┌──────────────────────────────────────────────────┐     │
│  │  :root {                                          │     │
│  │    --color-primary: 221 83% 53%;                  │     │
│  │    --radius-md: 0.375rem;                         │     │
│  │  }                                                │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │  inherits                     │
│                           v                                │
│  HOST ELEMENT                                              │
│  ┌──────────────────────────────────────────────────┐     │
│  │  <my-button>                                      │     │
│  │    (the custom properties are available)          │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │  inherits                     │
│                           v                                │
│  SHADOW ROOT                                               │
│  ┌──────────────────────────────────────────────────┐     │
│  │  button {                                          │     │
│  │    background: hsl(var(--color-primary));          │     │
│  │    border-radius: var(--radius-md);                │     │
│  │  }                                                │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The custom properties are the only cross-boundary channel.│
└────────────────────────────────────────────────────────────┘

The Integration Strategies

┌────────────────────────────────────────────────────────────┐
│  THE INTEGRATION STRATEGIES                                │
│                                                            │
│  INJECTED STYLESHEET                                       │
│  ┌──────────────────────────────────────────────────┐     │
│  │  The full Tailwind per instance.                  │     │
│  │  Simple, but the memory grows with the instances. │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  CONSTRUCTABLE STYLESHEET                                  │
│  ┌──────────────────────────────────────────────────┐     │
│  │  The shared sheet across the instances.           │     │
│  │  The modern recommendation.                       │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  CUSTOM PROPERTIES                                         │
│  ┌──────────────────────────────────────────────────┐     │
│  │  The component's CSS uses var().                  │     │
│  │  The application provides the values.             │     │
│  │  The smallest bundle.                             │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

The Lit Static Styles

┌────────────────────────────────────────────────────────────┐
│  THE LIT STATIC STYLES                                     │
│                                                            │
│  class MyButton extends LitElement {                       │
│    static styles = css`                                    │
│      :host { display: inline-block; }                      │
│      button {                                              │
│        background: hsl(var(--color-primary, 221 83% 53%)); │
│        color: hsl(var(--color-primary-foreground));        │
│      }                                                     │
│    `;                                                      │
│                                                            │
│    render() {                                              │
│      return html`<button><slot></slot></button>`;          │
│    }                                                       │
│  }                                                         │
│                                                            │
│  The static styles is the component's CSS.                 │
│  The custom properties are the theme bridge.               │
└────────────────────────────────────────────────────────────┘

Summary

ItemValue
Shadow DOMThe encapsulated style scope
:hostThe host element selector
::slotted()The light DOM children
Constructable stylesheetThe shared stylesheet
adoptedStyleSheetsThe adoption property
Custom propertyThe cross-boundary channel
var(--token, fallback)The token with the fallback
Lit static stylesThe framework CSS
unsafeCSSThe Tailwind in Lit
Token contractThe documented API

Key takeaways:

  • The shadow DOM creates a separate style scope. The document’s Tailwind stylesheet does not apply inside the shadow root. The component’s CSS must be inside the shadow root or adopted.
  • The custom properties are the only thing that inherits through the boundary. The --color-primary and the --radius-md set on the host element are visible inside the shadow root. The component’s CSS uses var(--color-primary), and the application provides the value.
  • The constructable stylesheets share the styles across the instances. The CSSStyleSheet is created once, and the adoptedStyleSheets property shares it. The browser parses the CSS once, and the memory is shared.
  • The var() fallback makes the component standalone. The var(--color-primary, 221 83% 53%) works without the application’s theme. The application overrides the value.
  • The Lit static styles is the framework mechanism. The css template is the component’s CSS. The Tailwind inside the Lit uses the unsafeCSS and the full stylesheet.
  • The :host and the ::slotted() are the shadow-specific selectors. The :host targets the host element, and the ::slotted() targets the light DOM children. The ::slotted() matches only the direct children.
  • The dark mode is the custom property change. The .dark class sets the different values, and the shadow root inherits them. The component does not need a dark variant.
  • The token contract is the API. The component documents the tokens it reads, and the application provides them. The documentation is the agreement between the two.

Remember: The Web Components and the shadow DOM encapsulate the styles, and the Tailwind stylesheet does not cross the boundary. The custom properties are the bridge: the application sets them, the host element inherits them, and the component’s CSS uses them. The constructable stylesheets share the styles across the instances, and the Lit static styles is the framework mechanism. The var() fallback makes the component standalone, and the token contract documents the API. The result is a component that is reusable across the applications, that inherits the design tokens, and that respects the encapsulation.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!