TL;DR Flattened trees are an implementation detail.
Consider this code snippet
<html>
<body>
<style>
my-element {
font-style: italic;
color: green;
}
p {
color: darkorange;
}
</style>
<p>Document Para</p>
<my-element>
<p>Light Child Para</p>
</my-element>
</body>
<script>
customElements.define('my-element', class extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'}).innerHTML =
`
<p>Shadow Para</p>
<slot></slot>
<style>
:host > p {
color: brown;
}
slot {
color: blue;
}
</style>
`;
}
});
const shadowHost = document.querySelector('my-element');
const shadowRoot = shadowHost.shadowRoot;
const shadowPara = shadowRoot.querySelector('p');
const slot = shadowRoot.querySelector('slot');
const lightPara = document.querySelector('my-element > p');
console.log(`Is Shadow Host the shadow Para's parent? ${shadowPara.parentNode === shadowHost}`);
console.log(`Is Slot the Light Child Para's parent?${lightPara.parentNode === slot}`);
</script>
</html>
Consider the styling consideration for the elements inside the Shadow DOM. Though the main document cannot style the <p>
element directly, inherited properties (font-style
and color
in this case) from the Shadow Host (<my-element>
in this case) are inherited by the Shadow tree elements. Looking at the cascade for the <p>
element inside the shadow tree

The rule with the selector :host > p
treats the <p>
elements as a direct descendant of the Shadow Host (my-element
). The operative word is treats. The <p>
elements is not a descendant in the DOM tree. Check the output in the console due to this code in the script
console.log(`Is Shadow Host the shadow Para's parent? ${shadowPara.parentNode === shadowHost}`);
Also note that the inherited properties of fonty-style
and color
are considered in the cascade for <p>
.
Same is the case with <slot>
elements. Though the actual <slot>
element is a direct descendent of the Shadow Root, the slotted elements (the <p>Light Child</p>
in this case) is not a descendant of the Shadow Root. Look at the cascade for the slotted element.

The element picks up the specified value for color
from the main document's style sheet but it also has the color:blue
property in the cascade too which is coming via the style on the <slot>
element. Again, the inherited property is treated as coming via the parent <slot>
even though the <slot>
element is not the parent of the light Para in the actual DOM.
console.log(`Is Slot the Light Child Para's parent?${lightPara.parentNode === slot}`);
The flattened tree, as you've rightly mentioned, is a construct used
in the CSS and event engine of the browser, as a representation to
model the parent-child relationship of styles on nodes but it doesn't
help with understanding the DOM tree relations amongst the elements.
The slot assignment would need to be figured out inevitably as the
elements from the Light DOM don't migrate physically into the
Shadow DOM, only references to these elements are held within the
Shadow DOM's <slot>
elements.
The Flattened tree is definitely a good visualization to understand
the styling part but would be hardly helpful to have it as the
replacement for existing DOM constructs like
HTMLSlotElement.assignedNodes()
etc.
Also, the spec duly points out this fact by mentioning (emphasis mine)
Loosely, the shadow tree is treated as the shadow host’s contents
instead of its normal light tree contents. However, some of its light
tree children can be "pulled into" the shadow tree by assigning them
to slots. This causes them to be treated as children of the slot for
CSS purposes.
... not for DOM purposes.