Needed: New “champion” for CSS @apply rule


#1

The CSS @apply rule no longer has a “champion” (in ES parlance), so it has essentially reverted to Stage 0 (though it is still behind a flag in Chrome), and it is on a path to extinction.

The champion of the feature was previously Tab Atkins, who has since moved on to a better solution for the shadowdom [citation, citation]. Unfortunately, this leaves regular “lightdom” elements without a possible feature to store a set of properties in a named variable, and then reference them in other style rules, reducing the amount of effort required to keep your stylesheet consistent as things change in the page. [citation].

If anyone wishes this feature to survive, it needs a new advocate within the CSSWG.


@apply CSS Rule in browsers
#2

I also very very much want to see @apply.


#3

Seconded, I’d love to see @apply happen, independently if its initial intention was for usage within the Shadow DOM or the Light DOM. Sadly I’m not a part of the CSSWG, but I would definitely love to contribute to the spec in any way I can though.

I’ll make sure to take a look at the current draft and see what I can do. Maybe we can also check with other members of the CSSWG via twitter and see if they would like to become the ‘champion’ of the spec?


#4

##What if we create extend/composes instead of @apply?

syntaxes flavour:

 /* 1) similar to pseudo selector :not(.class) */
.class-a:extend(.class-b) {}
.class-a:composes(.class-b, .class-c) {}

 /* 2) property style */
.class-a {
  extend: .class-b;
}

.class-a {
  composes: .class-b, .class-c;
}

.class-a {
  extend: eval(.class-b); /* in case browser need to use function to evaluate class like with css custom properties */
}

/* 3) similiar to :before & :after */
.class-a:extend {
  content: eval(.class-b, .class-c);
}

happy to hear any suggestions.

I am going for the first option because:

pros

  • it resemble :not(), this pseudo selector use selector as parameters
  • pseudo classes has multiple purposes like :before add an element
  • it resemble js extend: cons
  • maybe it increase specificity because is a pseudo selector
class Person extend Animal {}
.Person:extend(.Animal) {}

The main goal of this new feature are:

  1. non deterministic resolution
  2. reuse style, creating pattern
  3. no need to worry about specificity
  4. abstract css into css instead of html, create one source of truth and avoid dependency hell in html
  5. reduce css file size

Extending classes on html is dangerous, it creates an invisible amount of dependency and depend on css code order and specificity. On big code base changing classes on html, changing order of your css, changing css specificity are all possible cause of breaking something somewhere. Extend does not rely on code order or specificity and allow to abstract your css inside css not in html.

How does it work

When the browser parse this selector will create a .class-a CSS object

.class-a:extend(.class-b) {
   width: 100px;
   background-color: orange;
}

output =>
{
  width: 100px;
  background-color: orange;
}

When the CSS parser reach .class-b definition create an object and .class-a it will referenced it

.class-b {
   font-size: 12;
   color: red;
}

output =>
{
   font-size: 12;
   color: red;
}

/* now this selector will add .class-b styles */
.class-a:extend(.class-b) {
   width: 100px;
   background-color: orange;
}

output =>
{
   font-size: 12;
   color: red;
   width: 100px;
   background-color: orange;
}

The properties do not depend on code order or specificity. When class-b is parsed class-a will reference class-b.

specificity back again :frowning:

the only case specificity matter again if you write something like this:

<div class="class-a class-b">

but you do not need to because this is enough

<div class="class-a">

note:

If :extend is increasing the specificity because is a pseudo selector I would prefer to use a property.

.class {
  extend: eval(.class-b, .class-c);
}

long selector or multiple selector

.long selector #ciao .class-a:extend(.class-b) {}
.class-a:extend(.long selector #ciao .class-b) {}
.class-a:extend(.long selector #ciao .class-b, .long selector #ciao .class-c) {}
/* but it's better if you use single selector */
.class-a:extend(.class-b, .class-c) {}

extend(.first, .second) order matter

the order you define your classes inside the extend() matter but that can be useful to decide which pattern need to overwritten without depending of code order or specificity

.class-b { color: tomato; }
.class-c { width: 100px; }
.class-a:extend(.class-b, .class-c) {
  font-family: Arial;
}

output =>
{
  color: tomato; 
  width: 100px;
  font-family: Arial;
}

inheritance of custom properties

you can inherit custom properties from the other classes


.class-a:extend(.class-b, .class-c) {
  font-family: Arial;
}
.class-b { color: var(--b-color, red) }

.class-a {
  --color-b: tomato;
}

output =>
{
  color: var(--b-color, red);
  font-family: Arial;
}

or you can overwrite it

.class-a:extend(.class-b, .class-c) {
  color: black;
}
.class-b { color: var(--b-color, red) }

it’s one clock in London so I hope some American is awake and can give me some feedback goodnight :smiley: I will answer tomorrow(/today).

Note: In Android platform you can define inheritance inside style, with the property parent: https://developer.android.com/guide/topics/ui/themes.html#DefiningStyles


#5

I’m really glad that there’s someone who actually still wants to see this happen.

While I do love the enthusiasm and the fact that you took the time to compose such a thorough answer, I’m not entirely sure about proposing a new syntax/implementation when we already have a pretty big chunk of the spec figured out (or at least laid out).

I’ve been tweeting and looking for new advocates for the spec but so far I’ve found literally zero responses. Also, I’m not aware if this has already been discussed in the mailing list of the CSSWG, and if it hasn’t I have yet to do so.


#6

So, in order to update the status of this matter:

I opened an issue on GitHub to see if there was a member of the CSSWG interested in championing the spec. @fantasai read this issue and assigned @tabatkins to it, but as he said: “Assigning [Tab] to this issue is the opposite of what’s happening. ^_^”

I’m still not giving up, and over the next couple of days I will be emailing potential champions to see if they’re interested in the spec.


#7

Not arguing which syntax is better but this needs to happen. This functionality is in pretty much every css preprocessor which kind of proves its usefullness.


#8

I’ve asked if we can re-use ::theme(), only allowing the [part] attribute to be in CSS itself.

::theme(underline):focus {
  background-image: linear-gradient( var(--color, blue) );
  background-position: 0 100%;
  background-size: 100% 1px;
}

.headline-link {
  --color: rebeccapurple;

  part: underline;
}
```

This moves the apply-ness out of a css variable and into a selector, which I understood to be one of Tab’s complaints, which is also why I highlight it being used with `:focus`.

#9

Yes I think is good we have a github issue and I agree we should look for alternatives.

theme looks like a valid option to me. We are looking a solution for @mixin/@extend


#10

@apply going bye bye makes me sadpanda.


#11

So, I’ve been exchanging a few emails with some members of the CSSWG. It’s pretty hard to find someone with the time and/or funds to work on this, and it seems that right now the best solution would be to champion the spec by ourselves.

Thing is, I think we need to take a step back and think of our use cases (and the most common ones we can come up with), and maybe set some restrains with those as a starting point. I’m suggesting this as I believe it to be the best approach we could take in order to fix the caveats Tab found when working on the spec (explained in his blog post Why I abandoned @apply).


#12

Right, please read and digest my blog post there. @apply ends up being far too complicated, and its actual use-cases can be better served by more specific mechanisms.


#13

Yeah, instead of @apply for the light DOM, maybe go with something like…

https://tabatkins.github.io/specs/css-extend-rule/

instead?


#14

Totally agree


#15

To collect data, I’m informally asking Sass users on Twitter how they use mixins:

Sass users, why do you love mixins? Could you share examples of what you think are the most common or most vital use cases for them? https://twitter.com/jon_neal/status/907074296551440385

I’m also assembling some top Google results for mixin articles and listing out which existing or proposed web features would cover those use cases.

As far as PostCSS users go, this about summarizes it (cssnext still lists apply as a “future” feature):

^ if we wanted to make this more accurate, the distraction should be revealed to be a very poor choice, but I’m not clever enough to improve upon it.


#16

Rather than post on Twitter, I’ll share my general thoughts here regarding Sass mixins.

I personally have a relatively thorough set of mixins, ranging from simple responsiveness (defining multiple values in one line) to more complicated ones that write to multiple properties at once (a la size). One I’m quite fond of (though unrelated to this thread) is being able to define border-radius in the shorthand.

I don’t use @apply a ton (which is similar to @extend with pseudo-objects in Sass), but I certainly see the application, without speaking to the feasibility of implementing such a feature single-pass. It certainly seems like it’s difficult to implement, but obviously I’d love to see it just as much as the next person.


#17

I think that an @mixin at-rule would make a lot more sense than @extend. Not only would it allow for arguments to be provided, but it also plays well with my “import aliases” proposal:

@mixin newfeature(color; lengths; percent: 100%)
{
	-webkit-newfeature: var(color) var(lengths) var(percent);
	-moz-newfeature: var(color) var(lengths) var(percent);
	-ms-newfeature: var(color) var(lengths) var(percent);
	newfeature: var(color) var(lengths) var(percent);
}

button
{
	@apply newfeature(#FFF; 12px, 1em, 3cm; 12%);
}

Note:

  • Mixins would be distinguishable from custom properties on @apply by their lack of -- and their arguments list (which would be required even when there are no parameters/arguments.
  • Parameters would be distinguishable from regular variables by their lack of --.
  • One would be able to pass a list of comma-separated values as an argument because arguments and parameters are separated by semicolons and not commas.
  • Defaulted values would use a colon and be required to be at the end of the parameters list.

#18

I’ve forked the spec for the extend rule and (hopefully) simplified it. With the new spec, arguments could rely on existing CSS Custom Properties. Also, this would put the weight of the rule (previously on the rule containing the custom property set) back to the selector.

Here’s a demo of it in action (it is editable. Throw some variables in there to see how they would be managed):

And here is the forked specification: https://jonathantneal.github.io/specs/css-extend-rule/


#19

Good to see the conversation is still going strong. I’m going through all of the resources here and will jump back in if I see I can help with something.

@jonathantneal I like your approach very much. This simplification could very well be what its needed to make @extend possible. Will take an in depth look to the repo over the next week! :sunny: