-
Notifications
You must be signed in to change notification settings - Fork 679
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
[css-variables?] Higher level custom properties that control multiple declarations #5624
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<dael> Topic: [css-variables?] Higher level custom properties that control multiple declarations<dael> github: https://github.com//issues/5624 <dael> leaverou: There is a very reasonable tag guideline that custom elements shoudl use properties for presentational elements. With current state of custom properties this is impossible for non-trivial <dael> leaverou: Current custom prop can only be literal fragments and you need to transform <dael> leaverou: There are problems where we need to add inline conditionals. <dael> leaverou: However, when you have lots of these properties intersecting then it can get really messy if only 2 you have is inline funcitons that need both condisions. <dael> leaverou: Ideal is something to cascade but not sure feasible. Wanted impl feedback and then I can draft a more detailed proposal <dael> leaverou: Examples I've looks at from component liberties are in the issue <dael> leaverou: Some impl have weighed in in the issue [missed] <dael> leaverou: Wondering if set of constraints could be introduced to make it more feasable. Wanted to bring to attention of group for more ideas or thoughts <dael> astearns: Feedback on the shape of the feature? <dael> fremy: I think it's a pretty good idea. <dael> fremy: Really something that's a limitation of custom properties. Quite true when you use attributes you do more than reuse variable. <dael> fremy: Pseudo class you can't use in theory but it would be really nice to have syntactic sugar. I know you can do it meta languages. Good to auto-prefix with an if condition. That would give us most of advenatages. Extend the css selector syntax to have an id for all properties <TabAtkins> Assuming we're fine with simple conditionals in an if() function (which we've discussed before and this should be okay), doing an at-rule inside holding a block of props could have a reasonable desugaring to that. <dael> leaverou: One thing to keep in mind is these often need to control multiple elements in the component. Alignment might need to control margins and padding. Ideally should work <dael> leaverou: [missed] <TabAtkins> It would have some side-effects - any properties in the block are effectively using a variable, so it would kick in IACVT behavior, etc. <dael> leaverou: Often they need to control properties in mutli elements. Example alignment controls spaces, padding, etc in multi child elements. Good to keep in mind that it plays nicely with nesting module <dael> TabAtkins: invalid at computed value time = iacvt <fremy> @tabatkins: I would think it solves many issues <dael> leaverou: Some reasonable syntax to combine and falling back to invalid at computed value time would prob be acceptable <leaverou> As long as we can combine conditions and nest them, having IACVT as the ultimate fallback is acceptable <TabAtkins> so like `.foo { color: blue; @if (var(--state) = one) { color: red; } }`, it'd desugar to `.foo { color: cond(var(--state) = one, red; blue); }` <dael> astearns: I think this is a really interesting proposal and I'd like to see further discussion on what we can do here. Any major concerns about spending time on this? <dael> astearns: I think we should take this back to the issue and/or come up with a proposal which we can file issues on. IT's a really good idea. Anything else from group? <dael> leaverou: I primarily wanted to draw impl attention. I can't design impl needs. Continue on issue is fine |
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">Text</div>
</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>
<!-- customer does not own this component -->
<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<dael> Topic: [css-variables?] Higher level custom properties that control multiple declarations<jensimmons> Does that mean we get a CSS 2020 in 2020?? <astearns> just barely <dael> github: https://github.com//issues/5624 <dael> leaverou: I didn't explicitly add this. WE discussed last time and didn't get resolution. Interesting discussion in issue and off GH <leaverou> https://github.com//issues/5624#issuecomment-746339609 <dael> leaverou: I summerized current state in ^ comment <dael> leaverou: Summary: It looks like best course of action for block conditionals. Can't use pseudo class, casuse issues. If if() cascades have to carry extra context and increases too much complexity <dael> leaverou: Best is impl if based on idea of desugering to inline if calls and take into account properties in same rule. example in comment <dael> leaverou: Rasises some issues b/c certain values eval differently depending on prop. Hasn't come up that much. Length in some MQs <dael> leaverou: For example, ones we could come up with TabAtkins is %, em values, rem, lh, rlh, currentColor. <chris> rrsagent, here <RRSAgent> See https://www.w3.org/2020/12/16-css-irc#T17-34-13-1 <dael> leaverou: Problem. If it desugars to inline if calls nad conditional has relative values you may have cases where part of rule eval to true and a part of false. Example in comment. <dael> leaverou: Agreed don't want partial applicaitons. How to solve? <dael> leaverou: Came up with defining how these relative values would be evaluated. cureentColor is as if in color and so on. New inline conditional function to desugar iff <dael> leaverou: Doesn't sound good, but couldn't come with better <dael> leaverou: Addresses single conditional. Css nesting has same partial applicaiton problme. May have condition true for a rule but not decendnents. <Rossen_> q? <dael> leaverou: Might have var warning = on and a value for --warning on parent and different value on the child <dael> leaverou: You again have @if block applied paritially <dael> leaverou: Not sure if there's a way to address this. Couldn't come up with anything but just discussed yesterday. Don't know if there are ideas <dael> fantasai: What do you do if content has if clause with a property that effect evaluation. if on a em and evaluate em against font size <dael> leaverou: Can you put example in IRC? <fantasai> @if (var(...) > 1em) { font-size: 35pt; } <dael> leaverou: I see <dael> leaverou: I'm not sure <dael> leaverou: What would you suggest should happen? <dael> leaverou: It's basically same as if you have inline if <dael> Rossen_: In interest of time, are we ready to resolve or should we take it back to GH and continue there? <dael> leaverou: I suppose we could go back to issue <dael> Rossen_: Let's do that. Let's continue discussing there. I was hoping we were closer to resolution then we are. We'll come back |
@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>Save</my-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">button</button>
<button id="two">button</button>
<button id="three">button</button> /**
* ## 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. |
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(<dashed-ident> <comparison-operator> <value>)
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: