demo · v130

shadow tree vs flat tree ancestry

The chromestatus motivation: "authors would expect queries to work on the box tree and not skip containers in shadow trees where elements in the ancestor chain were slotted in." Chrome 130 switches container-query ancestor lookup from the shadow-including tree to the flat tree, so a named container in the light DOM is now reachable from inside a shadow root that received slotted content from inside it.

500px
share · save · print
slotted text — declares @container article inside its shadow root

two trees, two answers

shadow-including tree (pre-Chrome 130)

.article-layout [container: article] └── #shadow-root (inline-banner) └── .slot-text @container article — no match └── <slot name="text"> └── <span> (slotted from light) .slot-text's ancestor chain in the shadow-including tree exits via #shadow-root. The query container .article-layout is reachable in DOM, but the ancestor walk passes through the shadow boundary and the named container is invisible.

flat tree (Chrome 130+)

.article-layout [container: article] ├── .toolbar └── <inline-banner> └── .slot-text @container article — match └── <span> (slotted text — composed in) In the flat tree, slotted nodes are composed under the slot's host. .article-layout is a true flat-tree ancestor of .slot-text, so the named container query resolves and the styles apply.

the code

// Light DOM
<div class="article-layout">       /* container-name: article; container-type: inline-size */
  <inline-banner>
    <span slot="text">...</span>
  </inline-banner>
</div>

// Custom element shadow DOM
class InlineBanner extends HTMLElement {
  connectedCallback() {
    this.attachShadow({ mode: "open" }).innerHTML = `
      <style>
        .slot-text { padding: 1rem; background: var(--bg-paper); }
        @container article (min-width: 600px) {
          .slot-text { background: var(--accent-blue); color: var(--bg-paper); }
        }
      </style>
      <div class="slot-text"><slot name="text"></slot></div>
    `;
  }
}
customElements.define("inline-banner", InlineBanner);

// Pre-Chrome 130: .slot-text's @container lookup walks shadow-including
//                 tree, hits the shadow root boundary, NEVER sees .article-layout.
//                 Styles inside the @container block are dead code.
// Chrome 130+:    .slot-text's @container lookup walks the flat tree,
//                 finds .article-layout as a flat ancestor, queries match.

see also