w3c/css-houdini-drafts

[css-properties-values-api] Make all the descriptors optional

Opened this issue · 9 comments

kizu commented

https://drafts.css-houdini.org/css-properties-values-api-1/

For convenience I would talk about the CSS syntax, but the same could apply to the registerProperty() JS function as well.

Currently, all the declarations inside the @property are required (https://drafts.css-houdini.org/css-properties-values-api-1/#at-property-rule), (with the exception of initial-value, but only for universal syntax definition (*).

My suggestion: make all declarations optional, and if at least one descriptor is properly defined, the custom property should become registered.

Basically, each descriptor could have a “default”:

  1. syntax should default to the universal syntax definition. This would allow to conveniently set a variable to be non-inherited without explicitly typing it or setting an initial value.

  2. inherits should default to true, as this is the default for unregistered properties. There is no reason to require this descriptor to be explicitly required, and making it optional would allow to use the @property to more conveniently register properties that could be transitioned.

  3. initial-value should default to… not sure how it can be described, but basically, an “invalid” value. If the syntax is properly defined, then the property should still be able to be transitioned/animated, just both states should be defined, without a transition to or from the “invalid” value.

Overall, making all these optional would make the API much easier to use and would reduce potential human errors when registering the custom properties, and would lead to a more readable code overall, as for a lot of cases the property definitions would be more concise and containing only the things developers want to explicitly set.

I seem to recall that, for the JS syntax, there was an explicit choice to make inherits required, because it is unusual in JavaScript to have a missing object property treated as true by default. I don't entirely agree with it, but I understand the argument. I certainly agree that is unnecessarily verbose for the CSS syntax.

I also agree that initial-value and syntax should be optional (for both CSS and JS methods of registering properties), with the defaults Roma suggests.

In particular, it is very useful to support syntax without initial-value, to allow transitions/animations while also supporting fallbacks in the var() function for when the variable has not been set.

(This brings up another question: should there be an explicit way to indicate that the initial value is the guaranteed-invalid value, maybe initial-value: initial? But at the very least, it should be possible to preserve this behavior by not setting an initial value.)

All of these are required for good reasons. ^_^

As Amelia said, making inherits required is very intentional, to force authors to think about it. Inherited properties are more expensive than non-inherited, and given a choice, we probably shouldn't be defaulting to the more expensive option. Also, yes, having bools default to true is weird in JS and we should avoid it whenever possible.

Similarly, syntax is required because the "*" grammar is not very useful. We need "*" so we can support arbitrary values, but it's much better for us to guide authors to supplying a meaningful syntax that we can actually transition/etc when possible. If they don't need it, syntax:"*", is really short to type anyway.

And finally, initialValue is required whenever syntax isn't "*" because the guaranteed-invalid value doesn't match any specific syntax at all. We want to ensure that, if you say a custom property is a "<color>", you get a color when you query that property.

I'm open to allowing the guaranteed-invalid value as part of a custom property's value space; there's nothing wrong with syntax: "<color> | <guaranteed-invalid>". But I don't think it's good to make it easy to include it in your value space by default; I'd rather guide people (via the "missing key" errors) to set up their value space properly.

kizu commented

Well, I disagree that making the syntax more verbose to force authors think is a good idea.

If the intent was for authors to use non-inherited variables more — the default should have been false then (I personally would be all for it). Though, I guess, this is one thing that could potentially backfire: I imagine most authors would want to use property registration for transitions after they would already have some code, which could depend on inheritance. But, yes, I don't see anything bad in having a positive value for default, and explicitly disabling inheritance when needed.

But making authors write more when there is no real need is just an invitation for someone to come-up with shortcuts and extra build/runtime JS that would just do this. Add to this the possibility of a human error, where a typo in a “required, but not needed” part would end in everything not working. And — what are other places in CSS where there are required declarations like that?

Overall, I strongly do not like the idea of guiding authors as if they were blind puppies — especially for this kinds of advanced features. It is awesome that we have them, but sometimes the “good intentions” can make the feature much harder to use than it should have been. We need to have more faith in developers, and if we want to enforce good practices, it is the tools like browsers and code editors that could be used. Browsers could add profiling for the CSS variables performance and suggest not inheriting them (maybe it could be also detected automatically — if the inheritance is never used for a certain custom property, for example).

But maiming the syntax just so developers “would know better”?

Requiring explicitness when there's not an obviously correct-most-of-the-time (or at least obvious-most-of-the-time) default choice is a pretty standard language design pattern. Pretending that it's insulting to authors is incorrect and not helpful to this discussion.

kizu commented

Why have defaults anywhere then? Why gradients have a default direction? Borders — default thickness? backgrounds — default repeat? And so on? Or these were CSS design mistakes and if these would be designed today it would be different?

What are other places in CSS that require explicitness, especially on the level of whole declarations? And not just one required declaration — the need to have four different ones, without a shorthand, just to enable any of these declarations?

Right now custom properties registration can be used for these features:

  1. Enabling transitions/animations.
  2. Preventing inheritance.
  3. Providing a default value.

These features are not connected, but we can't use any one of them without explicitly mentioning the others. In my this coupling is unnecessary and introduces an extra barrier for usage of any of these, and an extra potential for a mistake or a type, where a mistake in one declaration makes the whole thing incorrect, which kinda goes against what CSS is currently is.

Why have defaults anywhere then?

Because API design is a complex and nuanced process, and no approach is correct 100% of the time.

and an extra potential for a mistake or a typo, where a mistake in one declaration makes the whole thing incorrect, which kinda goes against what CSS is currently is.

No, that sort of thing does occur at times in CSS. For example, @counter-style requires a 'system' descriptor, and either 'symbols' or 'additive-symbols', even tho we could theoretically default 'system' to, say, cyclic. The 'font' shorthand requires you to specify a font-family, even tho we could theoretically default it to serif (the initial value). Etc.

kizu commented

These are also longhand. And we can say font-size: 3rem without mentioning the font-family, when we want for it to fallback to inherited/default one. Same with most other shorthands — most would just work when you would provide just the crucial ones.

But when registering property we cannot do this and need to mention all of them even for smaller cases.

The CSS Working Group just discussed make P&V API descriptors optional, and agreed to the following:

  • RESOLVED: keep all fields in @property required
The full IRC log of that discussion <TabAtkins> Topic: make P&V API descriptors optional
<TabAtkins> github: https://github.com//issues/994
<fremy> AmeliaBR: with the at-property rule shipping, people started taking a look
<fremy> AmeliaBR: there were complaints that all the fields have to be explicit
<fremy> AmeliaBR: and this is not very css-y
<fremy> AmeliaBR: in general, css does handle defaults better
<fremy> AmeliaBR: and by making all these things be required, you remove some behaviors
<fremy> AmeliaBR: reasons why people use this
<fremy> AmeliaBR: - transition values
<fremy> AmeliaBR: - prevent inheritance
<fremy> AmeliaBR: - provide default value
<fremy> AmeliaBR: you don't always want all three
<fremy> AmeliaBR: and now you have to set all of them, which is not always useful
<fremy> AmeliaBR: worse, you cannot say "this is a color or it is invalid"
<fremy> TabAtkins: that default value thing is a different issue
<fremy> TabAtkins: I agree we should fix this
<fremy> TabAtkins: the other two cases
<fremy> TabAtkins: the inheritance one is that in JS, true has a default value is not logical
<fremy> TabAtkins: and we don't want to default to inheritance, because this is more costly
<fremy> TabAtkins: but having the default be the revert of the normal choice is confusing
<fremy> TabAtkins: so, we think forcing people to think about this is good
<fremy> TabAtkins: I think that issue is well settled, we really want to keep this in
<fremy> TabAtkins: for the syntax, we could use the unrestricted syntax "*" as a default
<fremy> TabAtkins: but I don't think that this is what you want
<fremy> TabAtkins: because it cannot do anything useful for you with this syntax
<fremy> TabAtkins: if we make this required, authors get an error, and they learn about this
<fremy> TabAtkins: and they realize they could have a better behavior
<AmeliaBR> q?
<fremy> TabAtkins: and "*" is not that common so I don't think it will be common enough to warrant the default
<fremy> TabAtkins: and there are other places in css where we force people to specify a value even if we have an obvious default
<fremy> TabAtkins: like for example fonts
<fremy> TabAtkins: we force you to say "serif" as fallback
<fremy> TabAtkins: even though it could be implied
<fremy> TabAtkins: but we thought forcing people to think about the fallback is useful
<fremy> AmeliaBR: you said that throwing an error makes more sense
<fremy> AmeliaBR: but in the non-declarative syntax, this works differently
<fremy> emilio: there are browsers where you can see CSS errors
<fremy> emilio: ;-)
<fremy> TabAtkins: I think this information is surfacable
<fremy> TabAtkins: if some browsers have a less-than-useful way of surfacing this, we can fix
<fremy> TabAtkins: but I still think "hey this doesn't work" is better than making it work less well
<fremy> TabAtkins: AmeliaBR do you feel about this strongly?
<fremy> AmeliaBR: not a huge deal I guess, won't object
<fremy> AmeliaBR: however, I think that the solution to the other issue is very important
<fremy> TabAtkins: yes, that's another issue, and there is another thread about that, and I think we are in agreement with what you wanted?
<fremy> AmeliaBR: can you send a pointer?
<fremy> TabAtkins: yes, issue ???
<astearns> https://github.com/w3c/csswg-drafts/issues/5370
<fremy> TabAtkins: so, it sounds like we should be able to resolve to close this issue no change for now
<fremy> TabAtkins: any final objection?
<fremy> RESOLVED: keep all fields in @Property required

Why have defaults anywhere then?

Because API design is a complex and nuanced process, and no approach is correct 100% of the time.

Come on folks, let's not pretend that inherits is required for API design purposes. That implies that there is a usability reason for this, which is false. If usability was the only factor, inherits would be optional and would default to true, because this is the default for non-registered properties. The reason it's required is performance, and thus, is a case of implementation affecting UI decisions, which is actually a usability anti-pattern, not to mention a violation of the Priority of Constituencies ("users before implementors").
I do understand the reasons, and I'm not saying we should make it optional, but telling @kizu that this is done for API design purposes is disingenuous. It's done to discourage use of a value that is performance intensive, plain as that.