A much-needed disclaimer: You (kinda) can use functions now! I know, it isn’t the most pleasant feeling to finish reading about a new feature just for the author to say “And we’ll hopefully see it in a couple of years”. Luckily, right now you can use an (incomplete) version of CSS functions in Chrome Canary behind an experimental flag, although who knows when we’ll get to use them in a production environment.
Arguments, defaults, and returns!
I was drinking coffee when I read the news on Chrome prototyping functions in CSS and… I didn’t spit it or anything. I was excited, but thought “functions” in CSS would be just like mixins in Sass — you know, patterns for establishing reusable patterns. That’s cool but is really more or less syntactic sugar for writing less CSS.
But I looked at the example snippet a little more closely and that’s when the coffee nearly came shooting out my mouth.

From Bramus in Bluesky
Arguments?! Return values?! That’s worth spitting my coffee out for! I had to learn more about them, and luckily, the spec is clearly written, which you can find right here. What’s crazier, you can use functions right now in Chrome Canary! So, after reading and playing around, here are my key insights on what you need to know about CSS Functions.
What exactly is a function in CSS?
I like this definition from the spec:
Custom functions allow authors the same power as custom properties, but parameterized
They are used in the same places you would use a custom property, but functions return different things depending on the argument we pass. The syntax for the most basic function is the @function
at-rule, followed by the name of the function as a <dashed-ident>
+ ()
@function --dashed-border() {
/* ... */
}
A function without arguments is like a custom property, so meh… To make them functional we can pass arguments inside the parenthesis, also as <dashed-ident>
s
@function --dashed-border(--color) {
/* ... */
}
We can use the result
descriptor to return something based on our argument:
@function --dashed-border(--color) {
result: 2px dashed var(--color);
}
div {
border: --dashed-border(blue); /* 2px dashed blue */
}
We can even use defaults! Just write a colon (
:
) followed by the default value for that argument.@function --dashed-border(--color: red) { result: 2px dashed var(--color); } div { border: --dashed-border(); /* 2px dashed red */ }
This reminds me of Adam Argyle’s experiment on a functional CSS concept.
Functions can have type-checking
Functions can have type-checking for arguments and return values, which will be useful whenever we want to interpolate a value just like we do with variables created with @property
, and once we have inline conditionals, to make different calculations depending on the argument type.
To add argument types, we pass a syntax component. That is the type enclosed in angle brackets, where color is <color>
and length is <length>
, just to name a couple. There are also syntax multipliers like plus (+
) to accept a space-separated list of that type.
@function --custom-spacing(--a <length>) { /* ... */ } /* e.g. 10px */
@function --custom-background(--b <color>) { /* ... */ } /* e.g. hsl(50%, 30% 50%) */
@function --custom-margin(--c <length>+) { /* ... */ } /* e.g. 10px 2rem 20px */
If instead, we want to define the type of the return value, we can write the returns
keyword followed by the syntax component:
@function --progression(--current, --total) returns <percentage> {
result: calc(var(--current) / var(--total) * 100%);
}
Just a little exception for types: if we want to accept more than one type using the syntax combinator (|), we’ll have to enclose the types in a type()
wrapper function:
@function --wideness(--d type(<number> | <percentage>)) { /* ... */ }
Functions can have list arguments
While it doesn’t currently seem to work in Canary, we’ll be able in the future to take lists as arguments by enclosing them inside curly braces. So, this example from the spec passes a list of values like {1px, 7px, 2px}
and gets its maximum to perform a sum.
@function --max-plus-x(--list, --x) {
result: calc(max(var(--list)) + var(--x));
}
div {
width: --max-plus-x({ 1px, 7px, 2px }, 3px); /* 10px */
}
I wonder then, will it be possible to select a specific element from a list? And also define how long should the list should be? Say we want to only accept lists that contain four elements, then select each individually to perform some calculation and return it. Many questions here!
Early returns aren’t possible
That’s correct, early returns aren’t possible. This isn’t something defined in the spec that hasn’t been prototyped, but something that simply won’t be allowed. So, if we have two returns, one enclosed early behind a @media
or @supports
at-rule and one outside at the end, the last result will always be returned:
@function --suitable-font-size() {
@media (width > 1000px) {
result: 20px;
}
result: 16px; /* This always returns 16px */
}
We have to change the order of the returns, leaving the conditional result
for last. This doesn’t make a lot of sense in other programming languages, where the function ends after returning something, but there is a reason the C in CSS stands for Cascade: this order allows the conditional result to override the last result which is very CSS-y is nature:
@function --suitable-font-size() {
result: 16px;
@media (width > 1000px) {
result: 20px;
}
}
Imagining the possibilities
Here I wanted everyone to chip in and write about the new things we could make using functions. So the team here at CSS-Tricks put our heads together and thought about some use cases for functions. Some are little helper functions we’ll sprinkle a lot throughout our CSS, while others open new possibilities. Remember, all of these examples should be viewed in Chrome Canary until support expands to other browsers.
Here’s a basic helper function from Geoff that sets fluid type:
@function --fluid-type(--font-min, --font-max) {
result: clamp(var(--font-min), 4vw + 1rem, var(--font-max));
}
h2 {
font-size: --fluid-type(24px, 36px);
}
This one is from Ryan, who is setting the width with an intrinsic container function — notice the default arguments.
@function --intrinsic-container(--inline-margin: 1rem, --max-width: 60ch) {
result: min(100% - var(--inline-margin), var(--max-width));
}
And check out this second helper function from Ryan to create grid layouts:
@function --layout-sidebar(--sidebar-width: 10ch) { result: 1fr; @media (width > 640px) { result: fit-content(var(--sidebar-width)) minmax(min(50vw, 30ch), 1fr); } }
This is one of those snippets I’m always grabbing from Steph Eckles’ smolcss site, and having a function would be so much easier. Actually, most of the snippets on Steph’s site would be awesome functions.
This one is from moi. When I made that demo using tan(atan2())
to create viewport transitions, I used a helper property called --wideness
to get the screen width as a decimal between 0
to 1
. At that moment, I wished for a function form of --wideness
. As I described it back then:
You pass a lower and upper bound as pixels, and it will return a
0
to1
value depending on how wide the screen is. So for example, if the screen is800px
,wideness(400px, 1200px)
would return0.5
since it’s the middle point
I thought I would never see it, but now I can make it myself! Using that wideness function, I can move an element through its offset-path
as the screen goes from 400px
to 800px
:
.marker {
offset-path: path("M 5 5 m -4, 0 a 4,4 0 1,0 8,0 a 4,4 0 1,0 -8,0"); /* Circular Orbit */
offset-distance: calc(--wideness(400, 800) * 100%); /* moves the element when the screen goes from 400px to 800px */
}
What’s missing?
According to Chrome’s issue on CSS Functions, we are in a super early stage since we cannot:
- …use local variables. Although I tried them and they seem to work.
- …use recursive functions (they crash!),
- …list arguments,
- …update a function and let the appropriate styles change,
- …use
@function
in cascade layers, or in the CSS Object Model (CSSOM), - …use “the Iverson bracket functions … so any
@media
queries or similar will need to be made using helper custom properties (on:root
or similar).”
After reading what on earth an Iverson bracket is, I understood that we currently can’t have a return value behind a @media
or @support
rule. For example, this snippet from the spec shouldn’t work:
@function --suitable-font-size() {
result: 16px;
@media (width > 1000px) {
result: 20px;
}
}
Although, upon testing, it seems like it’s supported now. Still, we can use a provisional custom property and return it at the end if it isn’t working for you:
@function --suitable-font-size() {
--size: 16px;
@media (width > 600px) {
--size: 20px;
}
result: var(--size);
}
What about mixins? Soon, they’ll be here. According to the spec:
At this time, this specification only defines custom functions, which operate at the level of CSS values. It is expected that it will define “mixins” later, which are functions that operate at the style rule level.
In conclusion…
I say it with confidence: functions will bring an enormous change to CSS, not in the sense that we’ll write it any differently — we won’t use functions to center a <div>
, but they will simplify hack-ish CSS and open a lot of new possibilities. There’ll be a time when our cyborg children ask us from their education pods, “Is it true you guys didn’t have functions in CSS?” And we’ll answer “No, Zeta-5 ∀umina™, we didn’t” while shedding a tear. And that will blow their ZetaPentium© Gen 31 Brain chips. That is if CSS lasts long enough, but in the meantime, I am happy to change my site’s font with a function.
Amazing! finally. now all that’s left if figure out the best use cases. also hopefully this could be incorporated into Tailwind to reduce the bundle size
Checking the date to make sure it’s not first of April or something … … … Nope. Mind blown!
I was waiting for an article on this.
What’s really impressive is all this is done via CSS Houdini!
I also love that it is basically an advanced custom CSS property, similar to how custom properties work but with some computations and a return value.
Pretty neat!
I even found some more information on CSS Houdini’s issue draft that goes into how this all started:
https://github.com/w3c/css-houdini-drafts/issues/857
Mennnnn. CSS Houdini is really powerful, and I would love to see the work on CSS through its powerful set of APIs soon. Maybe even write on some of them!
That’s really interesting!
If I had to choose CSS or SCSS, I always use to pick SCSS but we have
:root variables, @support and finally functions.
CSS has long way to go, hopefully until the Cyborg kids take over!
Nice one!
Am I right that function arguments can only be constants? Something like
won’t be possible?
Wow
Does it look like CSS4? Next version! I need to learn about CSS4.
Fn means more complex logic. Everyone got excited over it but anyone ever question about the possibility of maintaining?
Overflow feature already gave a good headache for newbie. Now we have a good headache for senior. Great.
Do you find overengineered CSS does not exist currently? Their authors did not wait for functions, they are already writing impossible mess. Of course, many people will abuse CSS functions to make horrible things, that’s the end result of all features, in CSS, in programming languages, in tooling, everywhere. Give them OOP and they’re going to dilute a simple feature into 50 layers of abstraction. Give them containers, and they’re going to make Kubernetes and its “fleets” and start speaking of “error rate”. Give them the idea of productified features, and they’re going to do microservices. In the end, it doesn’t matter if you don’t work with them, you can use the feature reasonably. The problem is never the tech you use, it’s who you work with.
Article didn’t explain how to activate the flag. When I downloaded Canary the Chrome Labs Erlenmeyer Flask Icon didn’t have CSS functions listed nor did the “chrome://flags/” URI.
After a bit of digging I managed to get this to work
Download canary from:
https://www.google.com/intl/en_uk/chrome/canary/
Run it with this command line: (Mac)
/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary --enable-features=CSSFunctions https://css-tricks.com/functions-in-css/ &
After that the CodePens run and it’s pretty cool what can be done.
Cheers for the article.