Skip to content

Host Element and Shadow Boundary

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.

: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.

: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.

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.

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 DOMShadow DOM
DefinitionChildren you put in the element from outsideThe private tree attached by the element itself
VisibilityNormal document treeEncapsulated, accessed via shadow root
StylingStyled by page CSSStyled by shadow CSS
SlotsProjected into shadow via <slot>Contains the <slot> elements

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.

What does the `:host` selector target inside a shadow root stylesheet?
You want the host element to appear semi-transparent when it has a `disabled` attribute. Which CSS rule achieves this from inside the shadow root?
A click event originates from a `<button>` inside a shadow root and bubbles to the light DOM. What is `event.target` in the light DOM listener?
Which of the following CANNOT cross the shadow boundary by default?