w3c / w3c/csswg-drafts

[css-cascade][css-scoping] Host layerable shadow roots

Open
#9,792 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

css-cascade-6 css-cascade-7
Dominant language
Bikeshed
Stars
4.9k
Forks
816
PR merge metrics
PR metrics pending

Description

I originally proposed this here: https://github.com/WICG/webcomponents/issues/909#issuecomment-1883480640. I talked with @mirisuzanne and she recommended we continue to flesh out this proposal here before bringing it back to WICG. What follows is more so geared towards one who has read/understands the CSS Cascading and Inheritance spec (see the original proposal for a more web-components-oriented perspective).

This is a proposal to allow cascade layers within an outer context (e.g. a document or shadow root) to style elements within an inner context (i.e. elements within a shadow root).

In regard to the Cascade, this would effectively bump the corresponding shadow context down in the sorting order to be within the outer context’s layers (i.e. sorted after whatever layer it was marked for access).

For authors of the outer context (e.g. a consumer of a Web Component using the Shadow DOM), this would mean greater styling flexibility than what is available through existing shadow root styling APIs (e.g. CSS Parts, slots, custom properties).

For authors of the inner context (e.g. an author of a Web Component using the Shadow DOM), this would mean reduced style encapsulation—but style encapsulation nonetheless:

  • Since the inner context’s styles would be cascaded after the permitted outer context’s layers, the inner context’s styles would take precedence unless the those layers were using !important.
    • This might seem a bit strange, but I think that it forces the consumer to understand that what they are overriding is an important default for the subsequent levels in the cascade. If the component author has set an element with “important defaults” as a CSS Part that might be a more appropriate API for the consumer to use to style said element where it’s actually used.
  • The inner context can use all: revert to effectively ignore styles from the host’s permitted layers for an element.
    • Again, this could be circumvented by the author of the outer context using !important.
  • The inner context would still remain isolated from subsequent layers (including unlayered styles) which would mean its own styling APIs using CSS Parts, slots, and custom properties would still be effective.
  • Allowing a component consumer to set some default styles can be advantageous for a component author:
    • Slots can be burdensome for their consumers and lose the benefits of DOM encapsulation for the component author.
    • CSS Parts might be too limited for a consumer and awkward to work with, especially with an existing system.

How this would work?

TODO: add better examples here

Here’s an idea for the CSS syntax (shadow() might not be great since drop-shadow() is a thing):

/* host's context (e.g. a document's stylesheet */
@layer defaults shadow(my-element), components, layouts;

@layer defaults {
  /* Anything here will apply to elements in `my-element`’s shadow tree */

  button {
    background-color: DeepPink;
  }
}

@layer components {
  /* Subsequent layers will not apply to the shadow tree’s elements */
}

@layer layouts {
  /* Subsequent layers will not apply to the shadow tree’s elements */
}

/* Unlayered styles (i.e. a subsequent layer) will not apply to the shadow tree’s elements */

In this case, the simplified expected sorting order (for elements in the shadow root):

  1. User agent styles
  2. User styles
  3. The host context’s defaults layer.
  4. The shadow context
  5. The rest of the host context’s styles starting with the layouts layer for the exposed elements (e.g. CSS Parts).

In cases where the shadow root was layered after the first layer, it would still receive the styles of the previous layer(s):

/* host's context (e.g. a document's stylesheet */
@layer defaults, components shadow(my-element), layouts;

@layer defaults {
  button { /* These will apply to `button` elements in `my-element`’s shadow tree */ }
}

@layer components {
  button { /* These will apply to `button` elements in `my-element`’s shadow tree */ }
}

@layer layouts {
  .some-layout button {
    /* These will not apply */
  }
}

For this case, the simplified expected sorting order (for elements in the shadow root):

  1. User agent styles
  2. User styles
  3. The host context’s defaults and components layer.
  4. The shadow context
  5. The rest of the host context’s styles starting with the layouts layer for the exposed elements (e.g. CSS Parts).

Use cases

TODO: add more use cases

  • It allows a component consumer greater styling control over a component than what is currently available through CSS Parts.
    • TODO: explain why CSS Parts is not a great API for this kind of styling (it’s good for other styling though).
  • It allows a component author a way to allow elements in their tree to receive “defaults” from the document while still preserving the component’s own styling API (whether that be using parts or custom properties).
  • It can allow legacy CSS design systems to apply to new custom elements that are using the Shadow DOM for templating.

Problems with this proposal

  • Priority does not translate to access. Using layers would be using them to do something beyond their design.
  • Cascading before doesn’t necessarily solve all potential use cases: more granularity might be required (i.e. being able to “slot styles” into different points within a shadow root’s cascade layers).
  • Reset styles tend to be far reaching and so applying a layer and all of its prior layers could have potentially ill effects.
    • This wouldn’t necessarily be an unsurmountable problem, but it could turn out to be a footgun.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read the CSS Cascading and Inheritance specification, the linked WICG proposal, and the referenced CSSWG discussion first. Flesh out the missing syntax examples and use cases, and clarify the listed problems and limitations. Done means the proposal is detailed enough for review and potential return to WICG.

Written by the indexing model from the issue text.

Assessment

Tech stack
css
Domain
frontend, web-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.