[Proposal] Expand inline style='' attributes to allow pseudo-class/element styling

style-attr
selectors
Tags: #<Tag:0x00007f8e4787da48> #<Tag:0x00007f8e4787d8e0>

#1

In a recent email to www-style, James Kyle asks for the ability to use inline styles (the style attribute) to style an element based on pseudo-classes. That is, specify inline how an element should look when it’s hovered/etc, just like you can do today for the general case.

This was proposed over a decade ago in the 2002 Style Attr draft, but it didn’t gain traction at the time and was dropped when the next draft was published in 2010. I’d like to revive this. As James points out in his email, Web Components and new DOM-handling techniques like React have made inline styles more useful than they were in the past, but it still requires going back to a stylesheet to apply hover/focus/etc styling to an element, which is frustrating.

The proposal is simple:

  1. Define a new grammar production for selectors composed solely of pseudo-classes and pseudo-elements. (There’s no need for any feature selectors in inline style; you already know what attributes/etc the element you’re modifying has.)
  2. Modify the grammar of the style attribute, to allow style rules containing the pseudo-only selectors to be placed after any inline style declarations. These rules are all scoped to the element they’re written on.
  3. Lightly modify the Cascade spec to allow for declarations that both come from inline style and have specificity, to get the obvious result - “plain” inline styles lose to selector’d inline styles, and selector’d inline styles use specificity like normal amongst themselves (so :hover {...} loses to :first-child:hover {...}).

This would look like:

<a href="http://www.w3.org/"
          style="color: #900;
          :link {background: #ff0}
          :visited {background: #fff}
          :hover {outline: thin red solid}
          :active {background: #00f}">...</a>

Note: In the past I’ve argued that intermixing style rules and declarations is grammatically ambiguous, and that’s still true in general. (The issue is that selectors like a:hover look like declarations.) In this case, tho, we’re limited to just pseudo-selectors, which all start with a :, which lets us distinguish them from declarations. This is identical to how at-rules are safe to intermix with declarations, because they start with @.


#2

Talking this over with Elliot Sprehn reminded that I’d forgotten some bits. In particular, this requires some non-trivial additions to the CSSOM, to expose the sub-rules.

Implementation-wise, this also becomes very difficult. In Chrome and WebKit, at least, style attrs don’t currently create “rules” internally; instead, the declarations in them are just spammed onto the list of declarations collected for the element from the cascade. This means we don’t currently need the concept of “a rule that applies only to a particular element”; this change would require us to add that concept.


#3

For what it’s worth, we could consider placing styles for pseudos in a separate attribute like stylepseudo if that simplified speccing and/or implementing:

<a href="http://www.w3.org/"
   style="color: #900"
   stylepseudo=":hover {color: red}">...</a>

In JS, this could be accessible, for example, this way (if the HTML part does not make difference, then just consider the JS part alone):

a.stylePseudo.classes.hover.color = 'red';

a.stylePseudo.elements.before.content = 'Some generated content';

#4

Nah, that doesn’t make a big difference.


#5

Regardless of what is done in HTML, having that functionality in JS would be incredibly useful. I have run into many instances where I have had to settle for more difficult solutions to problems solely because pseudo-elements aren’t part of the DOM.


#6

I definitely agree, having a method would be really useful to manipulate styles for pseudo-elements and pseudo-classes in an inline way, instead of inserting rules into a stylesheet.


#7

Syntaxwise I prefer the single attribute version than the multiple attribute version but looks like it’s a no go anyway. I would love JS access in but we run into issues here too even before hitting internal browser issues.

For a bit of a sanity check I tried to compare several rules cases and how they might look. Also because if you started with a cut down version anyway you’d want to avoid problems further down the line.

Pseudo class

CSS: a:hover{} Attribute: stylepseudo="hover:" JS: element.stylePseudo.hover. dropped the second level on stylePseudo, not sure it adds anything

Pseudo element

CSS: a::before{} Attribute: stylebefore="" JS: element.stylePseudo.before

Pseudo element with pseudo style CSS: a:hover::before{} Attribute: stylebeforepseudo="hover:{}" JS: element.stylePseudo.before.hover having the style on the element seems the more logical choice but if before is another CSSStyleDeclaration would we need to have another stylePseudo before hover?

Multiple pseudo classes CSS: a:hover:target{} Attribute: stylepseudo="hover:target:{}" JS: element.stylePseudo(":hover:target"). chaining fails for this instance so here’s an idea for how you might try and approach it, though it definitely feels wrong

Multiple pseudo classes and pseudo element CSS: a:hover:target::before{} Attribute: stylebeforepseudo="hover:target:{}" JS: element.stylePseudo(":hover:target").before. even this approach starts looking awkward here

With all that a JS method of getting to pseudo elements without also trying to deal with classes is cleaner, and potentially might be more doable (@tabatkins do Blink/Webkit have the same problems with pseudo elements as they do pseudoclasses?). Also it might be possible to extend it to be able to programatically read the content of these elements.


#8

I agree that the single attribute is preferable, but I’m not entirely sure how easily this could be implemented vs. the two attributes.

Regarding the JS attributes, I’m not entirely sure what would be the best approach for the implementation and for the user. I’m relatively new to the W3C’s CGs, so I don’t have a full understanding of how exactly everything is implemented on the backend.

What I would personally prefer is to have everything chained, like element.stylePseudo.hover.target.before, but this could cause issues with the hover and target, as they are commutable (unless we enforce a strict order, which would be different from what JS typically enforces).

I’ll be able to take a better look at the different variations later today, but at first glance I would prefer just chaining everything.

Edit: Looking back at the proposals, I would prefer to take a jQuery style approach that could also mimic the potential HTML implementation. Like this: element.stylePseudo(":hover:target:before") or element.stylePseudo("hover target before").


#9

One thing that would help is splitting pseudo classes from pseudo elements. The monolithic stylePseudo handling both directly makes it much harder and conceptually they are quite different.

Obviously bikeshedding names can come later but something like this approach could work (again not all of this may be doable in browsers but better to plan for this case and then see what could be implementable).

The stylePseudo on element should be the hook for accessing styles based on pseudo classes. These are difficult because as mentioned you can’t directly chain them. I like chaining but it just does not work here from a conceptual point of view, each object off of a state would have to have properties of both style (access to the styles) and stylePseudo (access to the available states). Worse than that I don’t think it’s even possible to write valid JS that works this way, presents the appropriate properties, has the effect intended and avoids being an unholy nightmare of an implementation so it’s really a no-go.

The element could then have a pseudoElements property which lets you walk to the desired pseudo element. These pseudoElements can then expose various properties and methods as needed eg:

  • style for the actual style properties
  • 'stylePseudoworks like the one on the class. Conceptually this is a bit weird as pseudoClasses apply to the element not the pseudoElement but it gives the cleanest API and reflects thestyle/stylePseudoofelement`
  • treating it as a pseudoElement properties allows you to add further functionality as needed or in the future such as a readContent method which could return the generated content string for the element

#10

Hi all, I’m curious about the usefulness of this - is it an attempt to render a page faster by compiling CSS and HTML together? Surely this wouldn’t be a joy to write, edit, or work around manually!

I have played around with this idea a but in the past - to fake this sort of thing is totally doable right now with a couple extra attributes and <20 lines of JS: http://codepen.io/tomhodgins/pen/ZQPRNE

After building that and playing with it, I am less convinced than when I set out that this would/could be useful. What is the imagined use-case?


#11

This is for programmatic access to styles on individual elements for JS powered modular frameworks such as React and definitely not aimed at manually writing.

These sort of frameworks drive style by state and to manage this will often directly manipulate styles on elements as needed but can’t access pseudo classes or states via JS at the moment.

To use a workaround involving dyamically amending stylesheets would be a huge overhead and ripe for nasty bugs and performance hits.

The reason both CSS and JS are being discussed is two-fold. Firstly using server-side rendering for first render is common so you’d need to be able to apply these styles in HTML on the given element if possible. Secondly you would want parity between an object’s style in JS and on the attribute.


#12

For example, yesterday I’ve written a user script that needs to apply hover styles to injected HTML. Having a separate accompanying user stylesheet just for setting hover style in addition to a small user script is not quite convenient in terms of management and maintenance.


#13

This seems like a perfect situation for something like:

<style>#thing:hover {color: red}</style>

Is there a reason an inline style tag in HTML wouldn’t work here?


#14

Just because something can be done doesn’t mean it can’t be done a better way.


#15

Using a STYLE element with CSS rules inside forces us to generate a unique nonconflicting id or class for element to be styled, while writing something like this right in JS:

  applyStyles(elem, {
      'background' : '#fff',
      'border'     : '1px solid #ccc',
      'box-shadow' : '0 0 10px rgba(0, 0, 0, .5)'
  });

is just simpler.

After all, we already do have ability to write inline styles, so we should just have the same ability as for pseudoclasses and pseudoelements.


#16

I like the syntax, but with one detail. I’m not sure having it as a function would be appropriate, but rather as a prototype.

elem.styles = {
    'background' : '#fff',
    'border'     : '1px solid #ccc',
    'box-shadow' : '0 0 10px rgba(0, 0, 0, .5)'
};

Passing an object like this would have the additional benefit of setting multiple styles at once, something that currently can’t be done without a library.

How does everyone else feel about this sort of syntax for JS? It could obviously be used for regular styles and pseudo-styles with no conflicts I can think of.


#17

I didn’t actually propose the function (that’d be offtopic), that was just an example of how I prefer to deal with styles in user scripts using a custom utility function instead of injecting a STYLE element.


#18

Gotcha. Regardless, I would love to see that in vanilla JS.


#19

Consider posting a separate proposal.


#20

“Fix” in HTML:

<a href="http://www.w3.org/"
   style="color: #900"
   style:link="background: #FF0"
   style:visited="background: #FFF"
   style:hover="outline: thin red solid"
   style:active="background: #00F">…</a>

<a href="http://www.w3.org/"
   style="color: #900"
   style-link="background: #FF0"
   style-visited="background: #FFF"
   style-hover="outline: thin red solid"
   style-active="background: #00F">…</a>

Probably fails for sub-element selectors like :nth-child(an+b), though.