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.

OptionBenefitCost
Event retargeting at the shadow boundaryOutside code can’t see the component’s internal structure through event.targetDebugging events is harder, since event.target at the host isn’t the actual element that was clicked inside the shadow tree
Style the host via :host / :host() from inside shadow CSSThe component controls its own box model (display, border) without depending on outside stylesOutside code can still override some properties on the host element (e.g. an inline style), so the outcome depends on who writes last
  • Expecting event.target in the page to point to the real element inside the shadow tree — event retargeting always rewrites event.target to the host element once the event bubbles out to the light DOM.
  • Using a plain selector instead of :host to style the host element itself — writing host-badge { border: ... } in shadow CSS and wondering why it doesn’t work. You must use :host, because the host element sits outside the shadow tree.
  • Confusing light DOM with shadow DOM — assuming content projected via <slot> lives in the shadow tree, when in fact the light DOM content still belongs to the caller and can’t be styled directly from the shadow root.

💡 Real-world example

Salesforce Lightning (LWC) — systematically uses :host styling so every component controls its own layout without depending on the page’s global CSS. (salesforce/lwc)

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?