javascriptcssweb-componentshadow-domlit-element

:host-context not working as expected in Lit-Element web component


I've got two Lit-element web components - one is units-list, which contains many units-list-item elements. The units-list-item elements have two different display modes: compact and detailed. Because the list element supports infinite scroll (and thus could contain several thousand units), we need any mechanism that toggles between the two modes to be as performant as possible.

That's why I thought an ideal solution would be to use the :host-context() pseudo-selector in the styles for the units-list-item element, as that way every units-list-item element could switch between the two display modes just by changing the class applied to an ancestor (which would be within the shadow DOM of the units-list element).

To elaborate, here's the relevant markup from the units-list element. Note that the "trigger" classes are being applied to the #list-contents div, which is part of the units-list template.

<div id="list-contents" class="${showDetails ? 'detail-view table' : 'compact-view table'}">
    ${units.map(unit => html`<units-list-item .unit="${unit}"></units-list-item>`)}
</div>

As you can see, the showDetails flag controls whether the "detail-view" or "compact-view" class is applied to the div containing all of the units-list-item elements. Those classes are definitely being applied correctly.

Here's the full render method from the units-list-item element (unnecessary markup removed):

render() {
    const {unit} = this;
    // the style token below injects the processed stylesheet contents into the template
    return html`
        ${style}
        <div class="row compact">
            <!-- compact row markup here -->
        </div>
        <div class="row detail">
            <!-- detail row markup here -->
        </div>
    `;
}

Then I have the following in the units-list-item element's styles (we're using SCSS, so the single-line comments are not a problem):

    // This SHOULD hide the compact version of the row when the 
    // unit list has a "detail" class applied
    :host-context(.detail-view) div.row.compact {
        display: none !important;
    }

    // This SHOULD hide the detail version of the row when the
    // unit list has a "compact" class applied
    :host-context(.compact-view) div.row.detail {
        display: none !important;
    }

My understanding of the :host-context selector says that this should work, but Chrome just renders both versions of the row every time, and the Chrome dev tools show that the selectors are never matching with either of the rows.

I know there are several alternatives that would work, but this is the only one I'm aware of that would allow the entire list of units to switch modes by changing a single class on a parent element. Every other solution I've considered would require, at the least, updating the class attribute on every units-list-item element in the list. I'd like to avoid that if possible.

Of course, my primary concern is simply to make this work, if possible, but I'm also curious about a couple of things and can't find any info about them. The two questions I can't seem to find an answer for are

  1. When :host-context is used within an element that is itself part of a shadow DOM, does it consider that parent element's shadow DOM to be the "host context", or does it jump "all the way out" to the document DOM?
  2. If it's the former, will :host-context jump multiple shadow DOM boundaries? Say I have a custom page element that contains a custom list element, which itself contains many custom item elements. If that item element has a :host-context rule, will the browser first scan up the shadow DOM of the list element, then, if matching nothing, scan up the shadow DOM of the page element, and if still matching nothing, then scan up the main document DOM to the <html> tag?

Solution

  • There is no support for :host-context in FireFox or Safari

    It will be removed from the spec: https://github.com/w3c/csswg-drafts/issues/1914

    One alternative is to use CSS Properties (those trickle down into shadowDOM)

    JSFiddle: https://jsfiddle.net/WebComponents/hpd6yvxt/

    Update Feb 2022

    https://caniuse.com/?search=host-context

    Update Feb 2023