Host Element and Shadow Boundary
The host element
Section titled “The host element”The host element is the element that owns the shadow root — for example, <my-badge> or <host-badge>. From inside the shadow tree, you can target and style the host element using the :host pseudo-class selector.
This is important because the host element itself lives in the light DOM. Normally, shadow CSS cannot reach into the light DOM — but :host is a special exception that lets the shadow tree style its own container.
The :host selector
Section titled “The :host selector”:host targets the host element from inside the shadow root:
/* inside shadow root <style> */:host { display: inline-flex; padding: 0.25rem 0.75rem; border-radius: 9999px; border: 2px solid #6366f1; background: #eef2ff;}Without :host { display: ... }, a custom element defaults to display: inline, which may not be what you want.
The :host() functional selector
Section titled “The :host() functional selector”:host(selector) lets you conditionally style the host based on its own attributes, classes, or state:
/* style the host when it has a [disabled] attribute */:host([disabled]) { opacity: 0.5; cursor: not-allowed; pointer-events: none;}
/* style the host when it has a specific class */:host(.primary) { background: #4f46e5; color: white;}This pattern is extremely common — component state (active, disabled, highlighted) is often communicated via attributes, and :host([attr]) is the right way to respond to them from inside the shadow CSS.
Shadow boundary for selectors
Section titled “Shadow boundary for selectors”The shadow boundary is strict for CSS selectors in both directions:
/* page CSS — CANNOT reach inside <my-badge> shadow root */my-badge p { color: red; } /* no effect on shadow tree */
/* shadow CSS — CANNOT reach outside into the light DOM */p { color: blue; } /* only affects <p> inside this shadow tree */The only exceptions are CSS custom properties (variables) and the ::part() pseudo-element, which are explicitly designed to cross the boundary.
Events and retargeting
Section titled “Events and retargeting”Most events bubble through the shadow boundary, but when they do, the browser retargets the event — the event.target seen by light DOM listeners is the host element, not the actual element inside the shadow tree that fired the event.
document.querySelector('my-badge').addEventListener('click', function(e) { console.log(e.target); // <my-badge> — not the <button> inside the shadow});This preserves encapsulation: light DOM code cannot see the internal structure of your component via event targets.
Light DOM vs Shadow DOM
Section titled “Light DOM vs Shadow DOM”| Light DOM | Shadow DOM | |
|---|---|---|
| Definition | Children you put in the element from outside | The private tree attached by the element itself |
| Visibility | Normal document tree | Encapsulated, accessed via shadow root |
| Styling | Styled by page CSS | Styled by shadow CSS |
| Slots | Projected into shadow via <slot> | Contains the <slot> elements |
Runnable demo
Section titled “Runnable demo”Two <host-badge> elements share the same shadow CSS. The one with the highlight attribute is styled differently via :host([highlight]).
The first badge uses the default :host styles. The second badge has the highlight attribute set, so :host([highlight]) changes its background to yellow and its border to amber.