CSS @function + CSS if() = 🤯

Support for Nested Container Queries and the CSS if() function inside CSS Custom Functions make @function very powerful.


This content originally appeared on Bram.us and was authored by Bramus!

In https://brm.us/css-custom-functions I took a first look at Chrome’s prototype of Custom Functions (CSS @function). Since then the prototype in Chrome got updated with nested container queries support and CSS if() also got added … and like I said: it’s a game changer

~

⚠️ This post is about an upcoming CSS feature. You can’t use it … yet.

This feature is currently being prototyped in Chrome Canary and can be tested in Chrome Canary with the Experimental Web Platform Features flag enabled.

~

# The quest for a light-dark() that works with any value.

The function I built in https://brm.us/css-custom-functions is a custom --light-dark() that can be used to return values depending on whether light or dark mode is being used.

@function --light-dark(--light, --dark) {
	result: var(--light);
	
	@media (prefers-color-scheme: dark) {
		result: var(--dark);
	}
}

Unlike the built-in light-dark(), this custom function is not limited to <color> values and works with any type of value. But also unlike light-dark() it cannot respond to the local color-scheme value and can only respond to the light/dark media preference.

See the Pen
Custom CSS Custom Functions: –light-dark()
by Bramus (@bramus)
on CodePen.

As hinted at the end of the post, this limitation can be removed once support for nested container queries and/or CSS if() got added to Chrome … and that day has come!

~

# A custom --light-dark() using Container Queries

ℹ️ Because this code uses container queries you always need a wrapper element. The next section that uses if() does not need this extra wrapper element.

Since my previous post the prototype in Chrome got expanded to also support nested container queries inside custom functions. This opens the path to allowing a per-element light/dark preference, like so:

  • Set a preferred color-scheme on an element using a custom property named --scheme
  • Rework the --light-dark() to use a style query to respond to the value of --scheme

The possible values for --scheme are light, dark, and system. When --scheme is set to one of the first two, the color-scheme is forced to that value. When set to system.

The function looks like this:

@function --light-dark(--light, --dark) {
	/* Default to the --light value */
	result: var(--light);
	
	/* If the container is set to "dark", use the --dark value */
	@container style(--scheme: dark) {
		result: var(--dark);
	}
	
	/* If the container is set to "system" and the system is set to "dark", use the --dark value */
	@container style(--scheme: system) {
		@media (prefers-color-scheme: dark) {
			result: var(--dark);
		}
	}
}

Inside the @function, the --light and --dark values are passed in as arguments to the function. The --scheme custom property however is read from the element on which the function is invoked.

To ensure that there is some value for --scheme, I set it on the :root depending on the prefers-color-scheme value. The value is also duplicated into a --root-scheme for further use.

:root {
	--root-scheme: light;
	--scheme: light;

	@media (prefers-color-scheme: dark) {
		--root-scheme: dark;
		--scheme: dark;
	}
}

To allow setting a preferred color scheme on a per-element basis, I resorted to using a data-scheme HTML attribute which I parse to a value in CSS using attr(). When the value is light or dark I use the value directly. When the value is system, the code uses the --root-scheme property value. To play nice with nested light/dark contexts the code uses @scope.

/* Allow overriding the --scheme from the data-scheme HTML attribute */
@scope ([data-scheme]) {
	/* Get the value from the attribute */
	:scope {
		--scheme: attr(data-scheme type(<custom-ident>));
	}
	
	/* When set to system, use the --root-scheme value (which is determined by the MQ) */
	:scope[data-scheme="system"] {
		--scheme: var(--root-scheme);
	}
	
	/* This allows the native light-dark() to work as well */
	:scope > * {	
		color-scheme: var(--scheme);
	}
	
	/* Because I chose to use these elements as extra wrapper elements, I can just display its contents */
	display: contents;
}

To learn about this attr(), go read CSS attr() gets an upgrade. As for @scope, it’s sufficient to read the quick intro on @scope.

With all pieces in place it’s time to use it.

In CSS:

[data-scheme] > * {
	color: light-dark(#333, #e4e4e4);
	background-color: light-dark(aliceblue, #333);
	
	border: 4px --light-dark(dashed, dotted) currentcolor;
	font-weight: --light-dark(500, 300);
	font-size: --light-dark(16px, 18px);
	
	transition: all 0.25s ease, border-style 0.25s allow-discrete;
}

In HTML:

<div data-scheme="light">
	…
</div>

Here’s a live demo. Remember that you need Chrome Canary with the Experimental Web Platform Features Flag to see the code in action.

See the Pen
Custom CSS Custom Functions + Nested Style Queries (+ attr()): –light-dark()
by Bramus (@bramus)
on CodePen.

~

# A custom --light-dark() using Inline if()

As of Chrome Canary 135.0.7022.0 the inline if() is also available behind the Experimental Web Platform Features flag. With this function you can omit the extra container element that the container queries approach needs, as you can conditionally select a value directly in a declaration.

The if() function also accepts style queries, so the overall approach remains the same: use a custom property and respond to its value. The resulting code however is much much shorter:

@function --light-dark(--light, --dark) {
	result: if(style(--scheme: dark): var(--dark); else: var(--light));
}

The code to set --scheme to light or dark also is shorter, as it’s more easy to fall back to the --root-scheme value.

:root {
	--root-scheme: light;
	--scheme: light;

	@media (prefers-color-scheme: dark) {
		--root-scheme: dark;
		--scheme: dark;
	}
}	

@scope ([data-scheme]) {
	:scope {
		--scheme-from-attr: attr(data-scheme type(<custom-ident>));
		--scheme: if(
			style(--scheme-from-attr: system): var(--root-scheme);
			else: var(--scheme-from-attr)
		);
		color-scheme: var(--scheme); /* To make the native light-dark() work */
	}
}

Usage remains the same as before, with the difference that you can set the color-scheme dependent styles directly on the [data-scheme] element.

[data-scheme] {
	color: light-dark(#333, #e4e4e4);
	background-color: light-dark(aliceblue, #333);
	
	border: 4px --light-dark(dashed, dotted) currentcolor;
	font-weight: --light-dark(500, 300);
	font-size: --light-dark(16px, 18px);
	
	transition: all 0.25s ease, border-style 0.25s allow-discrete;
}

Here’s a live demo to check out:

See the Pen
Custom CSS Custom Functions + Nested inline if() (+ attr()): –light-dark()
by Bramus (@bramus)
on CodePen.

~

# Conclusion

I was already very much excited about CSS Custom Functions by itself. Combining it with inline if() takes that to even a higher level.

Expressed through the Galaxy Brain (aka Expanding Brain) meme, this is how I feel about this:

The The Galaxy Brain (aka Expanding Brain) meme applied to CSS Functions

~

# Spread the word

Feel free to reshare one of the following posts on social media to help spread the word:

~


This content originally appeared on Bram.us and was authored by Bramus!


Print Share Comment Cite Upload Translate Updates
APA

Bramus! | Sciencx (2025-02-18T22:40:50+00:00) CSS @function + CSS if() = 🤯. Retrieved from https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/

MLA
" » CSS @function + CSS if() = 🤯." Bramus! | Sciencx - Tuesday February 18, 2025, https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/
HARVARD
Bramus! | Sciencx Tuesday February 18, 2025 » CSS @function + CSS if() = 🤯., viewed ,<https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/>
VANCOUVER
Bramus! | Sciencx - » CSS @function + CSS if() = 🤯. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/
CHICAGO
" » CSS @function + CSS if() = 🤯." Bramus! | Sciencx - Accessed . https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/
IEEE
" » CSS @function + CSS if() = 🤯." Bramus! | Sciencx [Online]. Available: https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/. [Accessed: ]
rf:citation
» CSS @function + CSS if() = 🤯 | Bramus! | Sciencx | https://www.scien.cx/2025/02/18/css-function-css-if-%f0%9f%a4%af/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.