A user account is required in order to edit this wiki, but we've had to disable public user registrations due to spam.

To request an account, ask an autoconfirmed user on Chat (such as one of these permanent autoconfirmed members).

Component Model CSS Brainstorming

From WHATWG Wiki
Jump to navigation Jump to search

Selectors Crossing Boundaries

Style rules from the host element's DOM tree are not normally allowed to cross into the shadow DOM. Conversely, style rules defined in the shadow tree are not normally allowed to cross over the content boundary back into the host element's tree.

The XBL way

The following is the the current "state of the art", but suffers from various drawbacks

XBL defines the allow-selectors-through and apply-author-sheets attributes on the binding template.

allow-selectors-through allows style selectors defined for the outer tree to cross the boundary into the shadow tree.

apply-author-sheets allows style rules defined for the outer tree to be applied entirely inside the shadow tree.

XBL furthermore defines the apply-binding-sheets attribute on content elements. If set, this allows style rules defined for the shadow tree to apply to the host element's children that are selected by that content element.

Finally, XBL defines the pseudo attribute on elements inside the shadow tree. Its content is an ID that then can be used in outside style rules as pseudo-element in order to style that element specifically. E.g., a <div pseudo="label"> inside a "x-foo" component could be styled by an outside rule 'x-foo::label { color: red; }'.

The XBL approach has several drawbacks:

  • It's an HTML solution to a CSS problem.
  • allow-selectors-through, apply-author-sheets and apply-binding-sheets break confinement.
  • allow-selectors-through and apply-author-sheets do not allow control over what kind of styles attributes to apply/let through.
  • pseudo-IDs are either very limited (XBL proposes to allow only value, choices, label, repeat-item and icon), or pollute the CSS pseudo-element namespace. If allowed arbitrarily they may clash across components, which would makes it hard for the CSS working group to define new pseudo-elements.
  • It is not clear whether the same pseudo-ID could be applied to several different elements
  • Associating a pseudo-ID with a specific element (elements?) makes it harder to later re-design the component without breaking using sites.
  • Pseudo-IDs allow selectors like x-foo::label div { color: red; } to style arbitrary elements of the shadow tree rather than just the designated element(s). This again makes it harder for component authors to later redesign components without breaking user sites.

Using pseudo-attributes and CSS apply

The following is a proposal that has not seen widespread discussion.

The following is similar, but not identical, to CSS variables and CSS constants.

An alternative to the XBL approach could be (for the lack of a better term) pseudo attributes. In this approach, a style rule may set a number of arbitrary attribute-value pairs that in and of themselves have no effect. However, a style sheet in the shadow tree can use a new CSS attribute apply to select which values to apply.

Example, using a prefix '::' for pseudo-attributes:

   x-tabbar {
       background-color:                   azure;
       ::label-backround-color:            lightblue;
       ::selected-label-background-color:  slategray;
   }

Within the component:

   #ShadowRoot
       <style scoped>
           x-label {
               color: apply(background-color);                             [1.]
               background-color: apply(color);
               color: apply(::label-color);                                [2.]
               background-color: apply(::label-background-color)           [3.]
           }
           x-label[selected] {
               background-color: apply(::selected-label-background-color)  [4.]
               ::tab-background: apply(::selected-label-background-color)  [5.]
           }
       </style>
       <div id="background">
           <x-label>Tasks</x-label>
           <x-label selected>Calendar</x-label>
           <x-label>Contacts<x-label>
       </div>
  1. These 2 lines revert foreground (text) color and background color. This example shows how apply could apply not only to pseudo-attributes.
  2. The foreground-color of non-selected label is set to the ::label-color pseudo-attribute, if defined. As this comes after the initial setting of the label color in [1], it gets precedence. If, on the other hand, ::label-color is not defined (as is the case in above example), this line has no effect.
  3. Overriding the background color of labels by the same mechanism as in [2]. Unlike ::label-color, ::label-background-color is defined in the example though, so this line would take effect.
  4. Setting the background-color of the selected label.
  5. This line forwards the passed-in ::selected-label-background-color pseudo-attribute as ::tab-background, presumably for use in nested components of x-label.

Advantages of this approach:

  • Avoids pseudo-element namespace pollution, while still allowing arbitrary names.
  • A given pseudo-attribute can be applied in various places in a stright-forward manner.
  • Pseudo-attributes can be applied directyl, or forwarded to nested components - the user does not need to know about the internal structure.
  • Pseudo-attributes that are not applied have no effect, giving the author control over how the component is stylable.
  • apply can be used for other effects, as shown in [1] in the example above.

Downsides:

  • Requires extension to the CSS spec.
  • General text & link-styling is unsatisfactory with this approach alone.
  • This only handles styling of specific elements of the shadow DOM, but does not address how the component could style children of the host element that are attached to a content element, nor how inheritance into/out of the shadow DOM is to be handled.

Inheritance Across Boundaries

For several use cases (e.g., components creating native-like widgets) it may be beneficial to specify how CSS inheritance works across host-shadow boundaries.

The XBL way

The following is AFAICT

In XBL, CSS inheritance crossses the flattened tree. There is no way to limit or steer this behavior.

CSS extensions

(WARNING: Inane scribblings!)

'::binding' sets the style to inherit for the shadow tree. By default, this is equal to the effective style of the host element.

   x-foo::binding {
       background-color: grey;  // overrides the background color (only!) for the shadow DOM
   }

On the shadow side, the ShadowRoot gets this styling as the styling to inherit from. By default, it inherits the styling.

Preventing automatic inheritance

The including document can reset the individual style-attributes to 'initial', to prevent the component from 'snooping' the outside styles. Conversely, the component can just override the style-attributes to prevent the outside document from interfering with its beautiful designs.

Now, having to reset every possible CSS attribute would be cumbersome, to say the least. In order to make this useful, attribute group names could be introduced, that set the style for multiple attributes at once. The only value a group can take is either:

   'initial': back to the initial value as determined by the UA
   'inherit': inherit from DOM parent

Also note that groups could be overlapping. E.g.,:

   all: sets all attributes
   color-group: sets color, background-color, border-color
   border-group: border-...
   background-group: background-...
   images-group: background-image, border-image, list-style-image [e.g., prevent image loading]
   text-group: color, font-family, font-style, font-weight, letter-spacing, quotes, text-align, text-indend, text-transform, word-spacing, white-space, 
   list-group: list-style-...
   table-group: empty-cells, caption-side,
   sound-group: azimuth, elevation, pitch, richness, speak-..., speech-rate, stress, voice-family, volume
   paged-group: orphans, widows
   visibility: not in a group (only contained in 'all')
   cursor: not in a group (only contained in 'all')

E.g.,

   x-foo::binding {
       images-group: initial;  // Don't allow '...-image' attributes to inherit into shadow DOM
       border-group: initial;  // Don't allow 'border-...' attributes to inherit into shadow DOM
   }

within the shadow DOM:

   <style scoped>
       :scope {
           border-group: initial; // Don't allow 'border-...' attributes to be inherited from outside document
           table-group: initial;  // Don't allow table-related attributes to be inherited from outside document
       }
   </style>

or

   <style scoped>
       :scope {
           all: initial;  // reset everything
           background-color: apply(background-color);  // ...except background-color (reapply the inherited value)
       }
   </style>

Other inherited attributes (e.g., text, list styles) would be inherited in this example. Also note that the above groups with the described behavior could potentially be useful even outside the context of shadow DOM.

A similar mechanism could be used to steer inheritance from the <content> element onto its selected children of the host element. The selection here is whether to inherit from the host element, or from the style set on <content>.

However, the problem here is that there are more options:

  • initial as by the UA (+user sheets)
  • original value as passed-in to the shadow DOM (only relevant to the shadow DOM side)
  • inherited from the host element (only relevant to the ouside document's side)
  • inherited from the <content> element

To this end we might specify additional values besides 'initial' and 'inherited':

   'original': back to the value set on the root node (equivalent to 'initial' outside shadow DOMs, the value on ShadowRoot inside shadow DOMs)
   'current': use the current render style - this is equivalent to 'inherit' outside the "interference" of a shadow DOM

On the shadow DOM side, "leaking" of styles can be prevented by re-setting attributes/attribute groups on the <content> element:

   <style scoped>
       content {
           images-group: original; // reset '...-image' attributes to as they were set on the ShadowRoot
       }
       content[clean] {
           all: initial; // reset everything to pristine initial values for a <content> element that has the (user) attribute 'clean'. 
       }
   </style>

On the outside document's side we could introduce yet another pseudo-element ::inside that specifies the style to inherit from (just reusing the name, since this pseude-element already has been proposed once):

   x-foo::inside {
       all: inherit;              // reset all styles to inherit from the host element
       background-group: current; // ... except for 'background-...' attributes, which are used with the current style (from <content>)
       white-space: initial;      // reset 'white-space' to the default value ('normal' in the case of 'white-space')
   }

Note: can't use 'x-foo > *' as selector here, because that would "miss" child text nodes!

Another issue:

  • Doesn't handle the case where a <content> element selects multiple different elements and wants to style different elements differently.

Styling the host element

(see also the rendering discussion page)

If the host element is rendered (this is the current default assumption.)

If the component is confined (this is the currently thought-of scenario).
This requires a second stylesheet in addition to any (scoped) stylesheet within the component.
If the component is not confined
The component's stylesheet can style the host element directly, not requiring a second stylesheet
The host element can be styled by stylesheets from 2 different scopes, requiring a resolution order. Most likely author > component > user > UA.
The components stylesheet could be written so as to style descendants of the host element. Preventing this adds additional complexity.

If the host element is NOT rendered This raises the question how styles that address the host element are to be handled. Most likely by storing the style on the host element, but applying it to the shadow tree instead, causing additional complexity

If the ShadowRoot is rendered (if it is an element instead of a separate node type)
...
If the ShadowRoot is not rendered
...

Styling <content> elements

This has similar issues and raises similar questions as styling the host element.

In addition, a content element may select multiple different child elements from under the host element. E.g., <content select="A B .left">, or a simple <content> that takes whatever isn't taken by other contents. In this case it's conceivable that different elements should be styled different. I.e., a single style assigned to <content> is not sufficient. XBL2's apply-binding-sheets allows to do this, but:

  • it can style any descendant, not just direct children
  • it breaks confinement

Approaches to solve this:

  • Allow the component to not just style the host, but also the host's direct children - i.e. don't see the problem as styling render children of <content>, but as styling HTML children of the host
  • Require wrapper components that do styling only, reducing the problem to styling the host element

Example for the 2nd suggestion: Rather than having

 <x-foo> ----------> [ShadowRoot]
   <A>                  <content> (A's in red, B's in green)
   <B>
 </x-foo>

require

 <x-foo> ----------------------------------> [ShadowRoot]
   <x-A> --------> [ShadowRoot] (red)           <content> 
     <A>              <content>
   <x-B> --------> [ShadowRoot] (green)
     <B>              <content>
 </x-foo>