CSS Conditions with Variables


#1

I’d like to propose a form of CSS conditions that makes it possible to apply a set of declarations if a condition with a CSS variable (custom property) evaluates to true. With a syntax like that:

@when       = @when ( <condition> ) { <declaration-list> }
<condition> = <var()-function> <operator> <declaration-value>
<operator>  = '=' | '<' | '<=' | '>' | '>=' | '!='

Which would look like this:

.foo {
	float: none;
	color: red;
	@when (var(--my-var) > 200px) {
		float: left;
		color: blue;
	};
}

How the values should be compared could depend on the type of the comparison values. So that 10.0px = 10px and 96px = 1in result in true. It might be also a good idea to support comparison of colors and other more complex value types.

I think there are many use cases for such a feature. Theming with CSS variables would be more powerful because a component could react to special cases like a higher letter spacing if a font size variable is set to a very small size.

There is already a workaround to achieve CSS conditions: http://kizu.ru/en/fun/conditions-for-css-variables/. But it can only be used for properties that support calc() and it’s quite hard to write compared to a simple condition syntax.


#2

–my-var is probably a value calculated by js, because you are using js already you could add a class and get the same result.

.foo {
	--is-content-query: 200px;

	float: none;
	color: red;
}

.foo.foo--is-content-query {
	float: left;
	color: blue;
}

my comment is to push you to create a stronger use case :slight_smile:


#3

CSS actually does have variables.


#4

–my-var is probably a value calculated by js

Why should that be the case? CSS variables are very useful without JS.

you could add a class and get the same result.

CSS variables are very different to classes, they inherit by default for example. That means you would have to set the class on every descendant element to get the same effect.

my comment is to push you to create a stronger use case :slight_smile:

I think better theming of components via variables is already a very valid use case. I also wrote a small article about how this feature might bring us some form of container queries: https://au.si/css-conditions-cq-alternative


#5

CSS actually does have variables.

Yes. This proposal is for adding a conditions feature to CSS that can be used with the already existing variables feature.


#6

Because right now css variable use static value and they do not reference the computed style value. You can use them statically for theming or dynamically with JS.

Yes you would need to add selector in the css but on html you need just one.

.component {
	width: 500px;
}

.component.is-content-query {
	width: 100%;
}

.component__element {
	float: left;
	width: 50%;
}

.component.is-content-query .component__element {
	float: none;
	width: 100%;
}

Probably I would be interested to see @when operator used to create a simpler way to apply certain style depending certain condition at run time or the ability to share the computed style with other classes. Maybe Houdini will be able to sort it out these problems.


#7

Sorry, I wasn’t clear. I was responding to @MatteoWebDesigner, who I thought was saying that variables were only handled in JS.


#8

Yes what I meant is one of the great advantages of css variables is evaluation at run time. It replace the use of inline style, you can define inside your css files how you want to apply css variables value. @when operator could enhance this feature and would make easier to create more sophisticated scenario. If we think in terms of static theming that is easy to accomplish with sass, browser vendor will never implement solution which are solved already.


#9

First off, I would LOVE something like this in CSS. I use variables extensively and I have needed this so many times. Yes, anything that can be handled with this can be handled by setting a myriad of classes on dozens of elements and their descendants, but it can produce much more elegant solutions.

HOWEVER, this suffers from the classic cyclical problem which causes the CSS WG to reject many proposals. Consider this snippet:

.foo {
	color: red;
	@when (var(--my-var) > 200px) {
		--my-var: 100px;
		color: blue;
	};
}

What color does this produce, red or blue? Probably the only correct solution would be if it keeps alternating between red and blue, and we resolved not to add such features to CSS a while ago.

So, while I would find this very useful, I think the chances of it getting accepted are slim. However, a slightly less powerful form of it might be accepted: A conditional that applies to the parent instead of the current element, so cycles are impossible because you cannot set a variable for the parent inside a given CSS rule. Many of my use cases would be solved by this lighter form, what about yours?


#10

Thanks for the great feedback!

The cyclical problem is a bit different from other proposals I think, because it’s not operating at the selector level but at declaration level. And for declarations there are already rules how to handle cyclic dependencies AFAIK. For example:

.foo {
	--a: var(--b);
	--b: var(--a);
}

This is specified to fail for both --a and --b and sets them to the initial/invalid value. I think the @when could work similar to @apply. For example:

.foo {
	color: var(--color);
	--color: blue;
	@apply --mixin;
	--mixin: {
		--color: red;
		--mixin: {
			--color: green;
		};
	};
}

This always resolves to red. I think the @when in your example would always be true, as long as the parent --my-var is larger than 200px, and color: blue should always apply.

If I’m wrong and @when has different cyclical problems then --* and @apply, I agree that applying the condition to the variables of the parent element would still make this feature very very useful.

Many of my use cases would be solved by this lighter form, what about yours?

Most use cases I could think of so far, are solved by the lighter form too. But if it’s possible to handle the cyclical problem, I would prefer the more powerful version.


#11

I wonder if the cyclical problem is less problematic in here. In element query the problem was parent size could depend on children size but in here you deliberately assigning a new value on the var, in this case is developer fault and it can be fixed.


#12

I feel that exposing existing values (already exposed in javascript) in CSS would be immensely useful. For example in javascript we can access offsetWidth and offsetHeight of any element. Now it would be great if there is a readonly (or even read write) system variable exposed in CSS for these. That would help prevent us having to maintain the variables and keep it updated all through the inheritance.

Also required would be a good syntax in referring values of these variables from other elements context. For example if one wants to set the width of an element based on the width of the parent - we should be able to access the parents variables. We may need to access siblings variables also - so the syntax should be generic enough to allow accessing variables of other elements. While this may be against encapsulation - we could evolve syntax to allow access and restrict access.

if --offset-width is the name given to the system variable equivalent of the offsetWidth of the element, then the css code could be

.element {
	width: var(--offset-width@parent)/2;
}

Note: this is just a suggestion of the syntax. If the @ symbol cannot be used here we can use some other special character.

Yes conditions would be useful, but I feel that should come after exposing the style variables available in javascript.

I read your other post on how this could help in container queries, but frankly if one has to modify a big existing site and introduce container queries in some small portion being modified - it would be a nightmare to use this approach as one has to go on maintain the variables right from the top.


#13

What about this case:

  • when .parent is small we want to stack our children
  • when .parent is larger let the children use their auto size
<div class="parent">
  <button class="child">button</button>
  <button class="child">button</button>
</div>
.parent {
  display: flex;
  background: orange;
  flex-direction: column;
}

@media (min-width: 500px) {
  .parent {
    flex-direction: initial;
  }
}

.child {
  display: block;
}

Instead of having the children define their size with a condition we can define the condition on the parent and use flexbox to decide how they are going to be layout out. css grid does calculation on its container size and decide how to layout their children depending their size (auto-fit, minmax). I think this solution does not suffer the cyclical problem:

.parent {
  display: flex;
  background: orange;
  flex-direction: if(min-width: 200px, initial, column) ; 
  // equivalent in javascript   
  // let flexDirection = width <= 200px ? 'initial' : 'column';
}

.child {
  display: block;
}

we could use it to create simple condition with css vars too

.parent {
   --state: true;
}

.child {
   color: if(var(--state), red, blue);
}

A more powerful approach would be to use if() with css grid, usually you would use media query

.grid {
  display: grid;
  grid-gap: 10px 20px;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr 1fr auto;
  grid-template-areas: 
    'header'
    'none'
    'none'
    'button'
  ;
  margin-top: 10px;
  padding: 10px;
  border: 1px solid gray;
  border-radius: 4px;
}

@media (min-width: 500px) {
  .grid {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto 1fr auto;
    grid-template-areas: 
      'header header'
      'none none'
      'button button'
    ;
  }
}

.grid h1 {
  text-align: center;
  grid-area: header;
}

.grid label {
  display: flex;
  align-items: center;
}

.grid input {
  flex-grow: 1;
  margin-right: 10px;
}

.grid button {
  background-color: #03a9f4;
  grid-column-start: 1;
  grid-column-end: span 2;
  grid-area: button;
}

with if() and other possible operators

/* option 1 */
.grid {
    display: grid;
    grid-gap: 10px 20px;
	
	grid-template-columns: if(min-width: 300px, 
		1fr 1fr,
		1fr
	);
	
	grid-template-rows: if(min-width: 300px, 
		auto 1fr 1fr auto,
		auto 1fr auto
	);
    
	grid-template-areas: if(min-width: 300px, 
		'header header' 
		'none none' 
		'button button', 
		
		'header' 
		'none' 
		'none' 
		'button'
	);
}

/* option 2 */
.grid {
    display: grid;
    grid-gap: 10px 20px;
	
	grid-template-columns: 1fr;
	grid-template-rows: auto 1fr auto;
	grid-template-areas:  
		'header' 
		'none' 
		'none' 
		'button'
	;
	
	@when (min-width: 300px) {
		grid-template-columns: 1fr 1fr;
		grid-template-rows: auto 1fr 1fr auto;
		grid-template-areas: 
			'header header' 
			'none none' 
			'button button'
		;
	}
}

/* option 3 */
.grid {
	grid-template-columns: 1fr;
	...
	
	@minwidth (300px) {
		grid-template-columns: 1fr 1fr;
		...
	}
}

/* option 4 */
.grid {
	grid-template-columns: minwidth(300px, 1fr 1fr, 1fr);
	grid-template-columns: maxwidth(300px, 1fr, 1fr 1fr);
}

Syntax wise I think we could think something more concise but I think what we are doing here is not far from css grid fit-content minmax

All examples are on jsfiddle: https://jsfiddle.net/b6gmo8mn/4/


#14

What about this case:

  • when .parent is small we want to stack our children
  • when .parent is larger let the children use their auto size

This sounds exactly like container queries (aka element queries) and there is already a discussion about it here: Element Queries

I think this solution does not suffer the cyclical problem

I don’t think that’s true. If you can change the style of an element based on the layout width of its parent you may end up in an endless recursion.

My proposal for CSS conditions is limited to CSS variables and I think this is very important to make it implementable for browsers. Getting access to properties like offsetWidth is a completely different topic I think and is not related to this proposal.


#15

It looks like CSS @apply was discarded, partly because of some problems it has with applying mixins that themself include other variables.

So I think @leaverou is right with the suggestion that the conditions should be resolved against the variables/properties of the parent element to make this feature possible to implement in browsers.

For CSS custom properties level 2 there is a chance that we get a parent-var() function that resolves variables against the inherited value. So it might be a good idea to only support parent-var() in the @when() conditions at first. It would still be a very powerful feature this way.


#16

Just a note to say that I ran across this today, which seems related:

https://tabatkins.github.io/specs/css-when-else/


#17

No, that’s not particularly related, it’s just a generalization of the existing conditional rules, plus conditional chaining.