-
Notifications
You must be signed in to change notification settings - Fork 719
[css-variables?] Higher level custom properties that control multiple declarations #5624
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I love the idea here because the conditions may be created and referenced from within CSS. This shares some thematic similarity with the custom |
I think we have to be careful with the naming of the expression, independent of how it's actually implemented. I like the approach of the @if-var but I really don't like the naming, as we don't know if we may expect constants or anything other than variables in CSS in the near or distant future. Thinking about it I'd like to have a universally e.g.
Thought even further it would be possible to redefine already existing properties. Like background kinda did for background-color. Imagine exposing the screen width or anything to the user agent with like That way it would be possible to combine conditions of each kind and even write media queries and media conditions as part of e.g.
|
@Que-tin Naming is typically decided at the end, all features are proposed with an implied "name to be bikeshedded". The problem with such a generic conditional is — as usual — cycles. Consider this:
Also, with such generic expressions, there are ambiguities. E.g. what does |
It would be good to hear from implementors about this, and in general solicit feedback about a possible way forwards that is implementable and covers most use cases, so I'm gonna go ahead and Agenda+ this. Note to chairs: I cannot attend the APAC call, so this would need to be next week. |
Good point. There are actually a few exceptions to make at this point, hence percentages as well as integers, should have no meaning at all in this case (seen relative to the element the condition is used in). It would be hard to consider when values are relative and when absolute to the element they are used in inside of the if condition. The advantages in my example above would actually be that you could use media conditions (if implemented as e.g. |
@LeaVerou Just to make sure I understand the proposal, for |
(... interpreting that upvote as a "yes"): Having selector matching ( IMO if we want to if-else on custom properties, then that evaluation needs to happen computed value time, i.e. if-else needs to be part of the value. Or we need something which translates into effectively doing that internally. Or, we add a new kind of custom property which can be known selector matching time. |
I agree with @andruud fwiw. I don't understand why something like this:
Is really more painful / complex than what's being proposed here. Basically, something like In fact if you make |
@emilio Idea: Would it be easier to implement if one was only able to set other custom properties in these conditionals? |
The CSS Working Group just discussed The full IRC log of that discussion |
As mentioned above, I would like this to be pursued further. That sounds very useful (but even having Random thought: another possible syntax for the syntactic sugar:
In the CSSOM, the @transform-values wouldn't be reflected, it would be a transform applied while parsing declarations.
|
So we'd previously discussed the
it would be equivalent to the following using
(I wrote The benefit of the at-rule syntax is that it inverts the grouping - when you have a bunch of variants of several properties, the at-rule groups them by variant, while If we treat the two as exactly equivalent, just sugar variations of each other, then this does end up implying some slightly non-obvious behavior in some cases. For example, in:
(Note the lack of "default" I don't think this is a big deal, it's just worth understanding the implications. I think this is much better than defining this as an almost identical feature that actually works completely differently under the covers. |
Regarding Tab's proposal, we can do slightly better, and copy the value preceding the
would be either |
Yes, that exactly what the first part of my post was implying - you'd collect a given property across all the if-blocks and "plain", and group them into a cond() (with the "plain" version being the final default branch of the cond()). |
I think this is a great idea and would find it very useful in our applications as we author them today. We might author a web component that provides an interface to change the I'd assume through a conditional, we can expose an additional interface that would inherit a custom property from an ancestor. @if(conditional: true) {
--prop: var(--new-prop);
} Based on the conditional, a component we author might take the shape of the following: <your-element>
#shadow-root
<style>
[part=foo] {
border-radius: var(--border-radius, 0);
@if(--theme: bubbles) {
/* inherits --app-border-radius from ancestor such as :root */
--border-radius: var(--app-border-radius, 15px);
}
}
style>
<div part="foo">Textdiv>
your-element> This would give our customers to control the behavior of Since CSS custom properties inherit through shadow trees, my assumption is a customer can gain better control by changing CSS contextually in their component. Their app might define :root {
--theme: bubbles;
@if(--theme: bubbles) {
--app-border-radius: 30px;
}
} But contextually, they want to target the prop from <my-element>
#shadow-root
<style>
::part(foo) {
@if(--theme: bubbles) {
--border-radius: 4px;
}
}
style>
<your-element>your-element>
my-element> I highlight this example to capture how Salesforce would share styles between components but expose additional control for our customers. |
@tabatkins How will nested conditions be written? I saw this example of yours in the linked issue of course I think commas make more sense here.
But what if I turn the cond around? This makes it quite unreadable in my opinion especially the comma-separated list of possible values. Imagine having more than two conditions nested in each other
Or will it be possible to do smth like this to increase readability? Should be, correct?
But how about the at-rule?
Or will it be smth like this:
May it be possible to also implement some kind of logical operators for the conditions? AND, OR and NOT would be amazing to have in both solutions. I mean, of course, it would be possible to achieve this even without the operators, it just would increase the readability a lot.
I think this is one of the most complex proposals up to date as there are so many different approaches and solutions as well as things that have to be paid attention to. |
IACVT could work for components, since presumably all their styles are defined in the same place. Assuming these rules can be nested with predictable results, and do take all values defined in the rule into account before triggering IACVT (per @FremyCompany and @brandonferrua's suggestions), I don't think the downsides of IACVT will come into play too frequently. However, especially in that case, nesting support becomes really important. I love the idea of using Indeed, |
A few issues with speccing generic comparison expressions: how are certain values interpreted outside of a declaration context? E.g. what are percentages relative to? If we disallow percentages, then I suppose we should define this as a union of specific types (and both sides of the comparison need to be of the same type), i.e. (this is mostly to @tabatkins but any input is welcome) Edit: Oh, actually, if we define this as just sugar for inline |
Hi, I'm new here but I would like to give feedback on this proposal that seems a powerful way to achieve many things in CSS. I would also like to stress to participants that CSS improvements can be used in other areas than custom components, and those use cases should be considered as well. There is a number of features that I find interesting in the
For example, here is how I would use it for mixins: * {
@if(var(--mixin-danger) = on) {
background-color: red;
}
}
#my-error-message {
--mixin-danger: on
} As for naming, I would not use Also, would such condionals be allowed to be nested ?
This would bring surprising behaviour where a property in your conditional could be applied but not the next one because the conditional applies differently to it. I think the sanest approach would be to compare string equality and only allow comparison on types that are independent of the context. If you define If you allow colors to be compared, then you'll get a problem when a custom property contains something that can be interpreted as a color but is not meant to be a color. it could have an equality match with a value where this is not expected. |
I'm not @tabatkins but hopefully your question is directed to the group and not specifically towards Tab? Parenthesizing the conditional like that makes for a very hard to read syntax any way you order these.
which I believe is more readable than any of the examples above, and more externally consistent (this is how inline conditionals work in CSS preprocessors (Sass Less), as well as in spreadsheets).
Nested my-input {
border-radius: if(var(--pill) = on, 999px, 0);
} I think what you meant to write was perhaps this: my-input {
border-radius: 0;
@if (var(--pill) = on) {
border-radius: 999px;
@if (var(--half) = on) {
border-radius: 10px;
}
}
} which would desugar to: my-input {
border-radius: if(var(--pill) = on, if(var(--half) = on, 10px, 999px), 0);
} |
@mildred: Mixins was one of the use cases I mentioned in the original proposal, however do note that implementing these as sugar on the * {
background-color: if (var(--mixin-danger) = on), red, unset);
}
#my-error-message {
--mixin-danger: on;
} This means that if you have something like this: * {
@if(var(--mixin-danger) = on) {
background-color: red;
}
}
div {
background-color: yellow;
}
#my-error-message {
--mixin-danger: on
} and a Unfortunately, the feedback we got from implementers is that if we make the rule cascade, it is much harder to implement. I'm unclear on whether there are any constraints that would make cascading conditionals implementable (either limitations in what they contain, or in the condition itself). The question is, if they do not cascade (outside the rule they are defined in), do they still solve a large number of use cases? |
Trying to write up an Unofficial Draft on this, I've come across a few issues. @tabatkins and I had a good discussion yesterday about them. I'm going to try and summarize the current status here. How to implement
|
The CSS Working Group just discussed The full IRC log of that discussion |
@LeaVerou Chances of that Edit: also read up on CSS mixins and the surrounding proposals related to that. The other half of this is basically that. |
What about having the computed variables of a container being one of the things that can be queried by container queries?
This doesn't really add any significant complexity*, since knowing the computed style (and even layout) of the container before evaluating the container query is already needed for normal (size) queries. We'd then avoid Emilio's concern with Element.matches. cc @mirisuzanne
|
Interesting. Would we be able to style |
Only descendants unfortunately, otherwise we get circularity problems. |
It's not ideal, but combined with an inline |
I don't think container-queries+ShadowDOM have been thought about thoroughly yet, but yes that sounds like what we'd want. |
That's the major use case, so whatever solution we pick needs to be able to work with that. |
Is the |
That should be possible, provided that we can define what the operators mean and how things should evaluate. For example, in
Mostly a matter of specifying the grammar + rules I think. Totally possible. |
Sure, but a lot of these rules are needed for |
I just noticed this discussion after @johannesodland mentioned here in my proposal (#7273) for addressing the same issues in a different way. I completely agree about the needs we have here as @LeaVerou explained. Do you find useful, a solution like the below by having <my-button>Savemy-button> my-button {
--size: small;
}
@media screen and (max-width: 900px) {
my-button {
--size: large;
}
} /* inside my-button component style */
:host {
padding: map-get((small: 4px 8px, regular: 8px 16px, large: 12px 24px), var(--size, regular));
} More details are in #7273. |
With the proposal in #3714 slightly extended for conditionals, a solution could look something like this: $pill {
border-radius: 999px;
}
my-input {
@include pill if (var(--pill) = "on");
} |
This can be implemented in CSS today using cyclic space toggles (I like to think of them like enums and switch cases). .foo {
--size: var(--small);
--theme: var(--dark);
--placement: var(--top);
--orientation: var(--vertical);
--significance: var(--warning);
} Although it isn't the most convenient, here's how something like /**
* ## CSS library author code: ################################
*/
button {
/* define the configurable option for the end user, with its default value */
--size: var(--size-medium);
/* define all the possible enum values for the option (commas are required, they enable the magic) */
--size-small: var(--size,);
--size-medium: var(--size,);
--size-large: var(--size,);
/* finally apply output values using a switch case inside of each output property */
border:
solid deeppink
var(--size-small, 1px)
var(--size-medium, 2px)
var(--size-large, 3px);
border-radius:
var(--size-small, 2px)
var(--size-medium, 4px)
var(--size-large, 6px);
padding:
var(--size-small, 2px)
var(--size-medium, 4px)
var(--size-large, 6px);
} Here's how an end user would use it: <button id="one">buttonbutton>
<button id="two">buttonbutton>
<button id="three">buttonbutton> /**
* ## End user code: ##########################################
*/
#one {
/* end user picks the enum value they want */
--size: var(--size-small);
}
#two {
--size: var(--size-medium);
}
#three {
--size: var(--size-large);
} where, depending on the picked codepen example: CSS enums and switch-case logic Roman Komarov has some nice articles on it here: https://kizu.dev/layered-toggles/ TLDR: yeah, maybe not entirely intuitive. But! Once the pattern is learned, it is easy to implement in libraries, and most importantly super easy for the end user to use. Using one of the syntax ideas above, here's the same thing in would-be new format: button {
--size: medium; /* possible values: small, medium, large */
border:
solid deeppink
if(var(--size) = small, 1px)
if(var(--size) = medium, 2px)
if(var(--size) = large, 3px);
border-radius:
if(var(--size) = small, 2px)
if(var(--size) = medium, 4px)
if(var(--size) = large, 6px);
padding:
if(var(--size) = small, 2px)
if(var(--size) = medium, 4px)
if(var(--size) = large, 6px);
}
#one {
--size: small;
} It is not a ton more terse, but the main advantage is that there is no cognitive overhead needed as with the space toggles. Someone who sees this code will know how it works without having to learn how space toggles and property cycles work. Paired with @property --size {
syntax: "small | medium | large";
inherits: true;
initial-value: medium;
} (namespaced property names highly recommended) |
I’m well aware of cyclic toggles, but they are a hack/workaround, not something we can be content about as a solution and have quite a lot of limitations, not to mention the awkwardness of actual values having to be hidden behind variables. |
Uh oh!
There was an error while loading. Please reload this page.
Currently, custom properties can be used to hold small pieces of data to be used as parts of larger values. Despite being called custom properties, they are mainly used as variables. High-level custom properties that control a number of other CSS properties cannot be implemented.
Besides limiting regular CSS authors, this makes it impossible for custom element authors to follow the TAG guideline to avoid presentational attributes and to use a custom property instead, except for very simple bits of data like fonts, colors, and lengths. For anything more complex, web component authors use attributes instead.
Examples from a variety of custom element libraries:
placement
attributesize
attribute in Shoelace and in Spectrumpill
attributeorientation
attributeleft
attributeplacement
,offset
,tip
attributesappearance
attributeI can collect more if needed, examples abound in nearly all component libraries. Currently, these are impossible to implement as CSS custom properties, for a number of reasons:
pill=on
vsborder-radius: 999px
).Essentially, component authors need more high-level custom properties that encapsulate the corresponding declarations better instead of just containing fragments of values.
Some proposals to address this problem focus on a JS-based way to monitor property changes [WICG/webcomponents#856], but that appears to be hard to implement. So, I'm wondering if we can address this from CSS instead, especially since it would also address a number of other use cases too that are unrelated to components, a big one being mixins, without the problems that we had with
@apply
.There are discussions in the group about inline conditionals [#4731, #5009]. If we were to have such conditionals, these would be possible to implement, but very painful (each declaration value would need to be one or more
if()
). I was wondering if we could simplify this.A pseudo-class such as
:if-var( )
would solve this ideally, but would likely not be implementable due to cycles. OTOH we already do cycle detection for variables, so perhaps it is? If so, I can flesh out a proposal.Otherwise, perhaps a nested
@rule
?With nesting, that would even allow multiple rules, so it would cater for use cases such as e.g. the tabs placement without too much repetition.
One way to implement this would be as sugar for multiple
if()
s, but that would have the undesirable side effect of all containing declarations being set toinitial
when the conditional doesn't match and there's no@else
, which is suboptimal.Whatever solution we come up with, some things to consider:
--size: [small | medium | large]
) or it may be used directly in some values, and also in conditionals.Current status (updated April 5th, 2021)
This is a long discussion, this is the current status:
if()
has several drawbacks and would end up being very confusing for authors.:const()
selector. This requires two cascade passes. Constants cannot be set based on:const()
selectors.The text was updated successfully, but these errors were encountered: