Skip to content

Shadow piercing Combinators in the Wild

Dimitri Glazkov edited this page Mar 10, 2015 · 11 revisions

The original intent of /deep/ was to provide a way to handle exceptional cases of when the scoping boundary is in the way of an occasional practical need to tweak styling from outside of the scope or globally.

Blink had been shipping the /deep/ combinator since M35. Here are some observations of the usage patterns in the wild.

####Positives

The /deep/ combinator provided a somewhat incremental workaround for existing CSS theming frameworks and empowered widget consumers to tweak pre-developed widgets styles to their liking. For example:

  • retrofitting Bootstrap to work with Shadow DOM is fairly mechanical (1, 2, 3)
  • it's relatively easy to spelunk into a widget to tweak its style (4, 5, 6)

####Negatives

Definitely seen the predicted abuse of the combinator. A couple of examples:

  • In two of the "positives" examples provided, the Bootstrap retrofits are effectively surrendering the style scoping boundary.
  • Polymer's layout.html is a set of attribute-based layout helpers, which uses /deep/ to apply in every tree. Given that this import loads by default in Polymer 0.5, the exception becomes the norm. Note: in Polymer 0.8, layout.html is no longer part of the core.
  • Third-party theming uses /deep/ to layer styles over existing UI library.

Interestingly, the first-party theming is actually moving away from using /deep/ and into more producer/consumer model.

####Not-unreasonable use cases

Overall, these seem like the scenarios where the need for /deep/ is reasonable:

  • Incremental adoption. I need to retrofit an existing CSS theming framework to reach into the scopes, created by shadow trees.
  • UA stylesheet equivalent. I would like to define a few "global" rules (for example, table { border-collapse: collapse; }).
  • Final tweak. I have a widget I like, but I need to tweak the style just so to meet my unique requirements.

####Seemingly-broken use cases

These scenarios seemed less reasonable:

  • Third-party theming. I would like to provide a service of theming a UI widget library beyond developer's intent.
  • Abuse of UA stylesheet equivalent. It is unnerving to see a the UA stylesheet equivalent grow too large. A few rules seems okay, but the benefit of the scoping boundaries diminishes rapidly as the number of rules grows.

####Alternative designs/solutions considered

A few alternative designs were considered in the past:

  • CSS Variables. Originally, Shadow DOM was envisioned to rely on CSS Variables (now known as CSS Custom Properties). This briefly worked in Chromium in 2012, when both were implemented behind flag. The developer feedback was that the CSS Variables were too fine-grained (per-property) to provide styling APIs for components, and the potential of property name collisions.
  • :part(). Introduced at W3C F2F in 2013, the part() function provided a way to define an explicit styling interface for a component widget. This was implemented in Chromium behind a runtime flag for 6 months. The reaction was that it was a step in the right direction, but not enough (1, 2)

After thorough exploration of the problem space with with various primitives, it might be good to give CSS Custom Properties a second look. The concern of being too fine-grained seems to be addressed by CSS Mixins, and per-scope property conflict resolution doesn't seem too far-fetched, given that ES6 Modules already have a similar concept.

####Performance discussion

One of the less-discussed benefits of style scoping is potential performance gains. Thanks to style scoping boundary, a typical component in the wild uses shorter selectors (mostly just type or class) and fewer rules per scope (1, 2, 3, 4). Additionally, style scopes act as style resolution boundary, which enables breaking up the process of selector matching into smaller, independent chunks of work -- something that could potentially lead to better style resolution performance. In this regard, the shadow-piercing combinators are contributing negatively: their presence forces the need for selector matching across scopes. It is unclear whether the performance gains will be significant at the moment.