Open jonathantneal opened 4 years ago
Looks like something for a preprocessor (SASS/SCSS, LESS).
This would basically reserve square brackets in properties to be used exclusively in conjunction with media queries. It certainly looks like a preprocessor target.
That is well pointed out, @CyberAP. This particular implementation is not feasible in actual CSS. I should have done more to point this out.
I want to reiterate that I’m looking for thematically similar proposals that would allow CSS authors to express conditional values in a more terse way. The hypothetical solution above and the attention it received effectively demonstrate this desire by authors, I believe.
Please forgive me if I butcher this next part. An additional request of mine is that we consider not (even passively) encouraging preprocessors to accomplish tasks that create syntactical interop issues with actual CSS. I can understand why it has been suggested; and your freely-given feedback means a great deal to me. My experience has been that popular userland experiments can positively and negatively impact standardization. When we encourage solutions to work alongside the language as much as possible, we drive experimentation that can lead to and help inform future standardization. However, if the interop is missing, sometimes those userland experiments actually get in the way. I believe both results have been the case with Sass and CSS (Sass has since been wonderfully responsive to interop issues). Again, this is something I should have done more to point this out in my original issue, and I hope my request does not discourage anyone.
EDIT: I changed " If that education is not there" to "However, if the interop is missing", because I think it makes a lot more sense. See, knew I’d goof it. 😬
Why not set a variable in a media query and express the other values as calculations over it? Presumably they're not completely random numbers, but there is some logic to them. Why not make it obvious?
Why not set a variable in a media query and express the other values as calculations over it? Presumably they're not completely random numbers, but there is some logic to them. Why not make it obvious?
It’s a good option that will work for some layouts, not for others. There’s not always a possibility to extract a logic across media queries.
So I'll start by saying I get the urge here - the current MQ situation is definitely a bit annoying. Either you have to duplicate your whole stylesheet into each MQ, meaning that the same rule shows up in different versions spread across the entire sheet (making it hard to ensure you keep everything up-to-date while editting), or you keep rules together but have to duplicate the MQ over and over again.
This syntax, tho, and likely anything close to it, is unworkable. It depends on the MQ you're differentiating on to be only a single condition's value, and the properties you want to set differently based on MQ to all be exactly the same (or else duplicate the same "initial" value across several of the clauses). And that's not even getting into the actual syntax shenanigans; maybe we could work this with a ;
separator, if it was always setting the whole value for the property.
That said, I think the core problem will get better if we keep pushing on MQ improvements, namely (1) letting you nest MQs into style rules, and (2) letting you set custom MQs or full custom at-rules.
The example above is a lot better if written as:
.text-box {
@media (--size:small) {
font-size: 24px;
padding: 50px;
margin: 50px 25px;
} @media (--size: medium) {
font-size: 20px;
padding: 30px;
margin: 25px 12.5px;
} @media (--size: large) {
font-size: 16px;
padding: 10px;
margin: 12.5px;
}
}
Or even, with a custom at-rule that just transforms itself directly into an @media rule:
.text-box {
@--small {
font-size: 24px;
padding: 50px;
margin: 50px 25px;
} @--medium {
font-size: 20px;
padding: 30px;
margin: 25px 12.5px;
} @--large {
font-size: 16px;
padding: 10px;
margin: 12.5px;
}
}
Or going a step further, with a custom function that evaluates a predefined MQ and selects a value accordingly:
.text-box {
font-size: --page-size(24px; 20px; 16px);
padding: --page-size(50px; 30px; 20px);
margin: --page-size(50px 25px; 25px 12.5px; 12.5px);
}
All of these should be possible in the future; the latter two would require a bit of JS to work, but not a complex amount.
I was recently thinking whether CSS Values should include a value type for an indexed set, array, tuple, group, enum, whatever with multiple entries of the same basic type. They would need special functions, of course, to access the values or a reduction of them. I think MQ-dependent values like these could make a valid use case for them.
@media screen and (max-width: 1200px) {
--index: 3; --screen: wide;
}
@media screen and (max-width: 768px) {
--index: 2; --screen: normal;
}
@media screen and (max-width: 425px) {
--index: 1; --screen: narrow;
}
.text-box {
font-size: calc(4px * (3 + var(--index)) );
padding: set(var(--index), 10px, 30px, 50px);
margin: set(var(--screen), var(--margin));
--margin: (narrow = 12.5px), (normal = 25px 12.5px), (wide = 50px 25px);
}
What about defining a switch function:
switch(<integer [1,∞]>, <toggle-value>#)
If the first argument is not positive, the value becomes invalid (yes it could start from zero too). The other arguments (I chose the toggle-value from the toggle()
function, not sure if there is something more general) are selected according to the first: 1
means the first toggle-value is returned, 2
the second, etc.
If the first argument evaluates to N and there are M toggle-values, with M<N, then the last toggle-value is used. The toggle-values could also contain var()
functions, if the switch is resolved before the vars are substituted/evaluated.
@Crissov I see that you edited your post to add a block which included this:
padding: set(var(--index), 10px, 30px, 50px);
If you have a specific proposal, I'd appreciate a description of it like I did with mine. Editing a previous post to add something that looks very similar to my proposal in the next message... does not help.
Hmm, a generic value switcher like that might make sense, yeah. Would let us hit the use-case pretty simply, without needing to wait for future Houdini stuff, and with a pretty minor syntax tax overall. Mechanics are no more complex than any other variable-related stuff.
Like I'd said earlier, pretty sure we'd need to use a ;
as the value separator there, but otherwise yeah it's all good. Functionally identical to just setting several variables in the MQ, but letting you avoid naming the temporary values, and keep them clustered at point-of-use rather than spread across MQs.
@carlosame I was just trying to explain “They would need special functions, of course, to access the values …” with pseudo-code. The “or a reduction of them” part is for instance found in #544, #905, #2826 and #4700.
If we are going to talk about a switch like function solution I think this conceptually fits with the existing switch/context proposal in that it seems like the underlying machinery could let you write tab's custom thing with some context property (which of course needs a whole topic discussion, but so does tabs I think) something like
@media (max-width: [1200px, 768px, 425px]) {
.text-box {
font-size: switch(
(breakpoint == 1) 24px;
(breakpoint == 2) 20px;
(breakpoint == 3) 16px;
);
padding: switch(
(breakpoint == 1) 50px;
(breakpoint == 2) 30px;
(breakpoint == 3) 10px;
);
margin: switch(
(breakpoint == 1) 50px 25px;
(breakpoint == 2) 25px 12.5px;
(breakpoint == 3) 12.5px;
);
}
}
So then this would let us unify underlying work and answer a bunch of questions 'together' rather than entirely disjointly. We could easily sugar this as something like..
@media (max-width: [1200px, 768px, 425px]) {
.text-box {
font-size: breakpoint(
24px;
20px;
16px;
);
padding: breakpoint(
50px;
30px;
10px;
);
margin: breakpoint(
50px 25px;
25px 12.5px;
12.5px;
);
}
}
But at least a lot of the harder questions can be centralized?
The main intent with my switch function is, precisely, to be able to somehow 'centralize' the switch at the place where the author is naturally putting the declarations, as @tabatkins mentions ("clustered at point-of-use"), reducing the verbosity of media rules.
So in a media rule you set an index-like property (there is also the possibility to mix it with some calc()
tricks), and also add all the rules that are really specific for that media query.
Then, you may want to put the switch at the "main" sheet, and for example if you have three index levels you do not need to always specify three values:
margin: switch(var(--mq-level); 50px 25px; 25px 12.5px; 12.5px);
font-size: switch(var(--mq-level); 24px; 20px);
Which also means that if you add another level and forget to update some declaration(s), things keep working.
Your suggestion (which is somewhat similar to the original proposal in this issue), instead, implicitly builds something similar to a matrix of values, that in principle have to be always fulfilled.
Finally, my switch allows for invalidating the declaration, which may sometimes be useful.
This whole switch thing sounds like a great resolution. I’m all for unifying underlying work, @bkardell, @carlosame, @tabatkins.
The CSS Working Group just discussed 5009 Express conditional values in a more terse way
.
Link to @bkardell’s switch() proposal from a different issue: https://gist.github.com/bkardell/e5d702b15c7bcf2de2d60b80b916e53c
If we do something like this, can it also cover use cases where you basically need a ternary instead of tying it to media queries? E.g. things like border-width: if(100% < 50vw, 1px, 2px)
. You can hack it with sign()
, min()
and max()
, but it's awkward.
Yeah, my argument was on the line of @leaverou's comment.
FWIW, an index-based switch(index; v1, v2, v3...)
function is just a couple lines of code, assuming that's also separately: https://hg.mozilla.org/try/rev/3744db19fd416ec99fbf96962811527f21e069e5 :)
One of the major objections during the call was the idea that we should just rely on nested MQs going forward, as that's already something we plan on doing anyway.
I don't think this is a good idea. We do need to be able to nest MQs inside of style blocks (for the same reason presented here, in fact!), for when adjusting to a MQ involves touching multiple properties at once. Trying to coordinate that by using a switch()
in each property independently would be both hard to write and hard to read, bad idea in general.
But when one is just adjusting a single property according to an MQ (relevant to us here: adjusting colors for the combo of {light, dark}x{high-contrast, low-contrast}), using MQs gets real verbose real fast, even if we pretend that custom MQs or custom at-rules are already a thing.
Here's a real-world example, taken from the W3C stylesheet. It's a bit long, so I can show off the effect of multiple color properties being affected:
.note {
border-color: #52E052;
background: #E9FBE9;
overflow: auto;
}
.note::before, .note > .marker,
details.note > summary::before,
details.note > summary > .marker {
text-transform: uppercase;
display: block;
color: hsl(120, 70%, 30%);
}
/* Add .note::before { content: "Note"; } for autogen label,
or use class="marker" to mark up the label in source. */
details.note > summary {
display: block;
color: hsl(120, 70%, 30%);
}
details.note[open] > summary {
border-bottom: 1px silver solid;
}
Here's the same code, using MQs and switch() to adjust colors for the aforementioned four combinations. (To avoid me having to actually do design work for the purpose of an example, I'm just using four arbitrary colors in every case.)
@media not (prefers-color-scheme: dark) and not (prefers-contrast: low) {
:root { --c: 1; } // light, high-contrast
}
@media not (prefers-color-scheme: dark) and (prefers-contrast: low) {
:root { --c: 2; } // light, low-contrast
}
@media (prefers-color-scheme: dark) and not (prefers-contrast: low) {
:root { --c: 3; } // dark, high-contrast
}
@media (prefers-color-scheme: dark) and (prefers-contrast: low) {
:root { --c: 4; } // dark, low-contrast
}
.note {
border-color: switch(var(--c); #52E052; #52E052; #52E052; #52E052);
background: switch(var(--c); #52E052; #52E052; #52E052; #52E052);
overflow: auto;
}
.note::before, .note > .marker,
details.note > summary::before,
details.note > summary > .marker {
text-transform: uppercase;
display: block;
color: switch(var(--c); #52E052; #52E052; #52E052; #52E052);
}
/* Add .note::before { content: "Note"; } for autogen label,
or use class="marker" to mark up the label in source. */
details.note > summary {
display: block;
color: switch(var(--c); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%));
}
details.note[open] > summary {
border-bottom: 1px switch(var(--c); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%); hsl(120, 70%, 30%)) solid;
}
And now here's the same code using nested MQs. To help it out, I'm going to go ahead and assume that MQ aliases exist, because otherwise it's just ridiculously verbose. (And similarly, I'm just spamming colors rather than do design work to actually do representative colors. ^_^)
@custom-media --light-high not (prefers-color-scheme: dark) and not (prefers-contrast: low);
@custom-media --light-low not (prefers-color-scheme: dark) and (prefers-contrast: low);
@custom-media --dark-high (prefers-color-scheme: dark) and not (prefers-contrast: low);
@custom-media --dark-low (prefers-color-scheme: dark) and (prefers-contrast: low);
.note {
@media (--light-high) {
border-color: #52E052;
background: #E9FBE9;
} @media (--light-low) {
border-color: #52E052;
background: #E9FBE9;
} @media (--dark-high) {
border-color: #52E052;
background: #E9FBE9;
} @media (--dark-low) {
border-color: #52E052;
background: #E9FBE9;
}
overflow: auto;
}
.note::before, .note > .marker,
details.note > summary::before,
details.note > summary > .marker {
text-transform: uppercase;
display: block;
@media (--light-high) {
color: hsl(120, 70%, 30%);
} @media (--light-low) {
color: hsl(120, 70%, 30%);
} @media (--dark-high) {
color: hsl(120, 70%, 30%);
} @media (--dark-low) {
color: hsl(120, 70%, 30%);
}
}
/* Add .note::before { content: "Note"; } for autogen label,
or use class="marker" to mark up the label in source. */
details.note > summary {
display: block;
@media (--light-high) {
color: hsl(120, 70%, 30%);
} @media (--light-low) {
color: hsl(120, 70%, 30%);
} @media (--dark-high) {
color: hsl(120, 70%, 30%);
} @media (--dark-low) {
color: hsl(120, 70%, 30%);
}
color: hsl(120, 70%, 30%);
}
details.note[open] > summary {
border-bottom: 1px silver solid;
@media (--light-high) {
border-color: #AE1E1E;
} @media (--light-low) {
border-color: #AE1E1E;
} @media (--dark-high) {
border-color: #AE1E1E;
} @media (--dark-low) {
border-color: #AE1E1E;
}
}
I think the results are pretty clear here - even with the significant verbosity savings from a @custom-media
, you're still turning every color-using property into roughly 8x as many lines, with tons of repetition across those (both repetition of the @media
lines, and the property names; I also slightly rejiggered the border
declaration in the final rule to avoid repeating the non-color values). The whole section goes from "easily fits on my screen" to "more than a screenful of content", with what is imo a lot of visual noise.
Contrast with the switch() example, where the color properties get longer (by a little more than 4x), but you don't gain any lines. (Longer starting declarations would probably encourage the author to linebreak between values, so you would increase the number of lines, but only by 4x, or maybe 5x if you keep the switch(var(--c),
on the first line and put the values on subsequent ones.) There's also a bare minimum of repetition; the only repeated bit is switch(var(--c),
in each value, because the logic behind the switching was centralized in the MQs at the start of the document and doesn't need to be repeated at each usage site.
And this was in just one small chunk of the W3C stylesheet - there are a lot more colors across the whole sheet than just these four instances. I think this example is both very realistic and fairly minimal; in real-world stylesheets I think there are often even more conditions than this.
If we do something like this, can it also cover use cases where you basically need a ternary instead of tying it to media queries? E.g. things like border-width: if(100% < 50vw, 1px, 2px).
Yes, we could. We could widen the grammar of the first argument to <integer> | <conditional>
(defining a calc-ish conditional with the same grammar structure as MQ/supports), and then if you use a conditional we select either the first or second value if it's true/false. (We'd probably allow more than two values, but third onward just would never be selected by a conditional argument.)
To be clear, as @bkardell asked me to clarify: The implementation in my comment above is basically the switch()
version (only I misunderstood @tabatkins, and I only used the semicolon for the index, so it's switch(<i>; v1, v2, v3, ...)
). It does handle CSS variables as in Tab's examples just fine.
Of course the fallback I chose is quite debatable (just return the last argument if the index is negative or out of bounds), and so on, but... :)
Yeah, I think an invalid index would instead be a parse failure (subject to being able to determine that - using var() would make it iacvt instead, using calc() would clamp it into the 1-N range implied by the rest of the values, etc).
I think an invalid index would instead be a parse failure
For consistency with calc()
& friends I think I would expect this to be addressed like in https://drafts.csswg.org/css-values/#calc-type-checking
the value resulting from an expression must be clamped to the range allowed in the target context. Additionally, if a math function that resolves to
<number>
is used somewhere that only accepts<integer>
, the computed value and used value are rounded to the nearest integer
If we think of this like a math function, yeah. If we think of it like a non-math function, then it would work as I said.
Either way works for me.
Authors may want a switch like this to apply by
1
, 2
, 3
1-2
, 3
and up<2
, >3
1
, foo
, bar
1-2
, foo | bar
Iʼm pretty sure there are reasonable use cases for all of these, even if we do not have generic key-value arrays in CSS (yet). The question is: should they all be supported within a single function?
(I hate that most programming languages only have single-value case conditions in their switch statement, but I actually like its cascading nature, although most people treat a break statement as mandatory within cases.)
Either way works for me.
I wonder @tabatkins why not keep what was specified in my proposal: clamp positive values (natural numbers) and invalidate for negative/zero.
It preserves the two desirable behaviours of explicit invalidation and clamping. Otherwise, you either have one or the other.
I only used the semicolon for the index
Using the semicolons allow commas to pass through the switch, which is better than the "toggle-values could also contain var() functions, if the switch is resolved before the vars are substituted/evaluated" hack that I suggested in my proposal.
Then, the post-index arguments can be <declaration-value>
instead of <toggle-value>
.
I'm unaware if other functions using the semicolon as a separator have been suggested.
Authors may want a switch like this to apply by [...]
All but the first of those would require a key/value association in the function, rather than just a list (implicitly keyed by index), which would make the grammar more complicated.
On the other hand, k/v is required for the layout-time switching from bkardell's proposal https://gist.github.com/bkardell/e5d702b15c7bcf2de2d60b80b916e53c, so maybe something can be worked out.
I wouldn't want to complicate the common case by requiring each branch to be explicitly tagged with an integer, tho.
I wonder @tabatkins why not keep what was specified in my proposal: clamp positive values (natural numbers) and invalidate for negative/zero.
It preserves the two desirable behaviours of explicit invalidation and clamping. Otherwise, you either have one or the other.
Precisely because it's one-or-the-other, and thus consistent. Can you explain why you believe clamping overly-large values is useful?
I'm unaware if other functions using the semicolon as a separator have been suggested.
Nope, this is new.
Can you explain why you believe clamping overly-large values is useful?
You won't typically clamp overly-large values, but values that exceed by one or two. I explained that in a previous comment, that you may not want to define a full matrix of values for every mq "level", and perhaps repeating the last value is good enough for quite a few definitions. And, copied from that post:
Which also means that if you add another level and forget to update some declaration(s), things keep working.
I explained that in a previous comment, that you may not want to define a full matrix of values for every mq "level", and perhaps repeating the last value is good enough for quite a few definitions.
Assuming that there's only one dimension you might want to omit (so you can put it at the end) seems a bit fraught, but...
Which also means that if you add another level and forget to update some declaration(s), things keep working.
...this is more interesting. I'm not entirely sure it's right tho - sometimes we do want things to fail obviously, to draw the author's attention to it and make it more likely to be fixed. Clamping makes the assumption that, in the case of this sort of mistake, the author would be okay with using whatever was last in their list, which has no guarantee of being appropriate to combine with the other values from the new set.
(Note that if we do go with "invalid", switch(calc(100000); ...)
will indeed clamp to the last valid index, so at least authors could activate the behavior manually if they want.)
sometimes we do want things to fail obviously
The declaration being invalid is not necessarily more obvious than the last value being applied. Depends on the document and the style sheet. Also, we are talking about media queries that could apply to media that is not being frequently tested/verified. The last value is the closest that we have to "author's intent".
switch(calc(100000); ...) [...] authors could activate the behavior manually if they want
If you mean putting calc()
at the switch itself, that prevents explicit invalidation because calc()
is supposed to clamp.
The last value is the closest that we have to "author's intent".
My point is that the last value isn't guaranteed to be closer to their intent than any other value.
That said,
Also, we are talking about media queries that could apply to media that is not being frequently tested/verified.
...this is pretty reasonable, and does lean me towards clamping rather than invalidating large values.
If you mean putting calc() at the switch itself, that prevents explicit invalidation because calc() is supposed to clamp.
Yes, that's what I was saying. ^_^
I’m all for unifying underlying work
I believe that a proposal like yours @jonathantneal or @bkardell's is not incompatible with the integer-based switch.
As far as I'm concerned I'd like to see proposals like those being further discussed (I even have my own flavour of it), although I see that kind of functionality as something that fits important use cases, but not a full/only solution to the issue.
does lean me towards clamping rather than invalidating large values
And also clamping negative/zero, or you handle them as invalid?
Zero/negative would always be an obvious authoring error, so it's appropriate to invalidate at that point. (Tho Oriol argues in https://github.com/w3c/csswg-drafts/issues/5009#issuecomment-625565859 that it would be best to consider this as part of the family of math functions, and thus automatically clamp at both ends. I don't think I agree.)
Zero/negative would always be an obvious authoring error
Not necessarily. Explicit invalidation is a potentially useful feature, forcing inheritance (on inherited properties):
@media (...) {
[foo=bar] {
--mq: 0;
}
}
font-size: switch(var(--mq);...);
Well, you're arguing for invalidation as well, so that's fine. ^_^
But also, in this case you could also use any non-number value to make it invalid; you don't need "0" specifically to have that behavior.
you don't need "0" specifically
True, although invalidating with "0" (or negative) allows conditional calc()
tricks when defining the custom property.
So... having had this conversation, including CSSWG and several follow on conversations I would like to revise my take from yesterday since my examples included a vector in the MQ to provide the context and I understand this better from several angles now...
Thinking about the two things being interchangeably referred to as switch()
together is definitely useful. Seeing this case and the comments means that @javifernandez and I will continue to think about whether something like this could work - I can't see any reason it couldn't
@media not (prefers-color-scheme: dark) and not (prefers-contrast: low) {
:root { --c: 1; } // light, high-contrast
}
@media not (prefers-color-scheme: dark) and (prefers-contrast: low) {
:root { --c: 2; } // light, low-contrast
}
details.note > summary {
display: block;
color: switch(
(--c == 1) hsl(120, 70%, 30%);
(--c == 2) hsl(120, 70%, 30%);
default hsl(120, 70%, 30%);
}
}
I believe that is useful and super-powerful, but I also agree that if you have exactly this case (not uncommon) and not some more complex expression, that is unnecessarily verbose and inherently more complex. So, I think there's a lot of value (separately) to the 'index based' function that I agree with @tabatkins is actually different from an authors perspective and maybe from an implementation one too. Unfortunately with kind of a lot of thought I don't see any way they can both be called switch()
and if they were it would be unnecessarily complex - I guess that's why we have things like if
and switch
in languages which do kind of similar things in different ways... I think here too both are valuable.
I think you could make a compelling argument for either of them to be called 'switch' tbh, but I not-entirely-mildly prefer to keep my thing called switch as I lean toward 'it is slightly more switch-like in ways that matter to me' and, to a lesser extent because it's been circulating and I feel like delivering something else called that now will add confusion.
@emilio seemed to imply he wasn't attached to the name, a quick straw poll of people thought that this seemed rather like if()
, but idk... can we at least agree that they do different things and continuing to call both switch()
seems bad? case()
? index-switch()
? something else?
For me the idea in a switch statement is that you have some conditions defining different cases, and you choose the 1st case whose condition holds.
That applies to the container queries switch()
, but not to the switch()
in this issue. This one is just picking the nth value among a list of values. Calling this switch()
would be confusing IMO, I agree it needs another name. Maybe something like pick-nth()
, nth-value()
, get-at()
or similar.
I don't see any way they can both be called switch() [...] to a lesser extent because it's been circulating
Your gist was created 5 days before I posted my switch (I was unaware of your other switch), and this is not a lot of time to circulate. I do not think that any of the two owns the "switch" word, and my opinion is that if any of the proposals is adopted by the CSSWG (and that remains to be seen), the name should be decided based on the convenience of CSS authors, and not "trademark wars".
That said, the proposal that has quite a few conditionals on it (and to me, looks like a multiple if()
), is yours :slightly_smiling_face:.
IMHO we should focus on creating proposals that are useful to authors, and not on telling other people to change the names of their stuff.
For me the idea in a switch statement is that you have some conditions defining different cases, and you choose the 1st case whose condition holds.
A switch with multiple conditions is not what people using C, Java, etc. are used to. Could you point to an example of a widely-used "switch" that has multiple conditionals?
Could you point to an example of a widely-used "switch" that has multiple conditionals?
Well one could argue that the conditions are implicitly defined to be equality checks between a fixed value specified at the top and another value for each case.
So maybe you don't specify the full conditions explicitly, but at least they are there in the idea. Picking the nth value in a list can be converted into a switch form but it seems a different idea to me.
https://en.wikipedia.org/wiki/Switch_statement#History says that the origin of the switch is the "definition by cases":
"#F. The function
φ
defined thusφ(x1 , ... , xn ) = φ1(x1 , ... , xn ) if Q1(x1 , ... , xn ), . . . . . . . . . . . . φm(x1 , ... , xn ) if Qm(x1 , ... , xn ), φm+1(x1 , ... , xn ) otherwise,
where
Q1 , ... , Qm
are mutually exclusive predicates (orφ(x1 , ... , xn)
shall have the value given by the first clause which applies) is primitive recursive inφ1, ..., φm+1, Q1, ..., Qm+1
.
So there you are, Q1 , ... , Qm
are the conditions.
For a specific programming language example, the following is a common pattern in JS:
switch (true) {
case cond1(): /* ... */ break;
case cond2(): /* ... */ break;
case cond3(): /* ... */ break;
}
origin of the switch is the "definition by cases"
Yes, cases on a single value/expression. But have you read the other proposal? Cases are independent conditions. Let's put it this way: which of the following two blocks of code looks more like a C switch:
int mq;
switch (mq) {
case 1:
return value1;
case 2:
return value2;
case 3:
default:
return value3;
}
which is conceptually similar to my switch, or this:
grid-template-columns: switch(
(available-inline-size > 1024px) 1fr 4fr 1fr;
(--foo == 1) 2fr 1fr;
(--bar < 3) 1fr;
default 1fr;
);
This last block is valid according to the other "switch" syntax. I mean, there are multiple <switch-condition> <css-value>
pairs, and nowhere says that <switch-condition>
has to operate on the same variable nor with the same comparison operator.
Well, your 1st block of code is an actual C switch and the 2nd one is CSS, so it's unfair. But your CSS switch
doesn't look like that at all. It be converted into that form, but IMO conceptually it's closer to vector::at
.
And while in C switch
doesn't allow the conditions of your 2nd block, in JS you can. And the concept is basically the same.
Minor nit: default
is equivalent to (0 < 1)
so it doesn't seem worth it to add special keyword for that. default
just adds cognitive burden IMO (what happens if it's not the last conditional?). It's likely easier to implement without that special case too.
it's unfair. [...] in JS you can
Well, what is probably unfair is to put a JS example where an if()-else block is shoehorned to a switch.
In any case, I'm not the one taking the final decision on what will be adopted, but saying that my function is an if()
and not a switch()
(while the other proposal would be a 'real' switch) is biased at best.
So there's three distinct functions being bandied about here.
The one mentioned by Carlos, where you provide a list of <any-value>
s and an index, and it resolves to the nth value; this function can be used anywhere. It's also almost certainly var()-like (in that it makes the property always-valid at parse time as long as the overall function is valid, turns on iacvt behavior, etc.)†
color: nth-value(var(--x); red; blue; yellow);
(There's a minor variant where you supply a value, and then pairs of values to match against and <any-value>
s, selecting the first pair whose match-value is identical to the first arg. This allows for named keys, which can be more readable in some cases than just a list, but is also more verbose for technically no additional functionality. I'll consider this off the list for now, but keep it on the back-burner just in case.)
color: nth-value(var(--x); (light-low) red; (dark-low) blue; (light-high) yellow);
The one mentioned by Lea, which is a math function you provide pairs of calc()-ish comparisons and calc()-ish results, and it resolves to the first value whose comparison succeeds; this can be used anywhere a calc() can. (Just like the other math functions, we can tell what its type will be at parse-time by examining the calculations and grammar-check that accordingly, so it doesn't need to be var-like.)
margin-left: cond((50vw < 400px) 2em, (50vw < 800px) 1em, 0px);
The one mentioned by Brian, where you provide pairs of comparisons (more powerful than what calc() can access; basically what a Layout Worklet is provided) and <any-value>
s, and it resolves to the first value whose comparison succeeds; this function can be used in a restricted set of safe properties that are "downstream" of the values it's allowed to compare over. (Most CSS properties are "safe", tho.) It's also almost certainly var()-like.†
grid-template-columns: switch(
(available-inline-size > 1024px) 1fr 4fr 1fr;
(available-inline-size > 400px) 2fr 1fr;
(available-inline-size > 100px) 1fr;
default 1fr;
);
All three seem to do something useful and distinct; none of them can reasonably be folded into the other without losing significant amounts of functionality.
As I did in the examples above, I suggest for the purposes of discussion we call (1) nth-value()
, (2) cond()
, and (3) switch()
.
† If we restrict the functions to only be usable as the whole value of a property, they don't need to be var-like; the UA can instead just grammar-check each of the <any-value>
s against the property's grammar at parse-time. This may be a reasonable imposition. That said, nth-value()
will really only be used with a var()
or similar as its first argument anyway, so the question is moot in practice for it; it's still potentially relevant for switch()
, tho.
Also, fwiw, I did some exploration on handling switch()
with an at-rule instead of a function, because I think the function flirts with too much visual complexity: https://gist.github.com/tabatkins/7d0d55fe08812d28cc46bed40b9deacb
2 and 3 seem pretty similar to me. Just to clarify, is it right that the only differences are:
If so, 3 just seems like a more general version of 2, rather than "none of them can reasonably be folded into the other". Or am I missing something?
Also, fwiw, I did some exploration on handling switch() with an at-rule instead of a function
I can see the utility, but it would be good to also have a quick function, for cases where it really is a one-off. The @rule can be too much repetition for a number of common cases.
Which active (or yet reviewed) proposals might allow CSS authors to express
@media
conditional values in a more terse way?The following hypothetical code was widely shared among Twitter users this last week:
This particular hypothetical solution is described by the author as a “media query [that] is an array of sizes and your property value arrays are linked to those sizes” *.