The Scope of CSS @function – Master.dev Blog
/ BLOG
@functionCSSif()
The Scope of CSS @function
Jane Ori<br>on<br>June 16, 2026
We’re going to walk through some advanced patterns for using @function in CSS in this article that help you deliver awesome DX to your component or library users. You’ll need an understanding of CSS function foundations and limitations to follow along with the gold here.
The Fundamentals and Dev Experience of CSS @function
The Scope of CSS @function
The Variable Scope of Custom CSS Functions
Variable scope in custom CSS @ functions is really fascinating. In normal CSS, --vars inherit from parent elements down to child elements. The children can freely use the inherited --vars.
html {<br>--theme: light;<br>--size-1: 1rem;<br>html div {<br>--palette-1: if(<br>style(--theme: dark): black;<br>else: white;<br>);<br>font-size: var(--size-1);<br>}Code language: CSS (css)
Similarly, in JavaScript, a function defined inside of a context inherits all of the variables from the context it’s defined in, to be used freely within the function. Colloquially in JavaScript, this context that variables are inherited from is called the closure scope .
let html, div
html = () => {<br>const theme = "light"<br>const size1 = "1rem"
div = () => {<br>const palette1 = theme === "dark" ? "black" : "white"<br>const fontSize = size1<br>return { palette1, fontSize }
return { theme, size1 }<br>}Code language: JavaScript (javascript)
Traditional CSS var() usages relies heavily on patterns and expectations from your DOM structure.
That is, DOM structure determines what context a child expected to exist under and therefore determines which vars you can guarantee will be inherited from the parents and safe to use in the child.
CSS Functions Have a Fascinating Superpower with Scope
They experience an inherited sort-of-closure scope from wherever they’re called. This exposes their internal work to all of the variables in any context they’re called from, as if they were a child element in the DOM and inherited them!
In JavaScript, it’s as if the definition for the child function was kept as a string and passed through eval() wherever it’s used.
An evaluation scope , so to speak.
let html, div
const fn = `() => {<br>const palette1 = theme === "dark" ? "black" : "white"<br>const fontSize = size1<br>return { palette1, fontSize }<br>}`
html = () => {<br>const theme = "light"<br>const size1 = "1rem"
div = eval(fn)
return { theme, size1 }
html().size1 === div().fontSize<br>// trueCode language: JavaScript (javascript)
In CSS:
@function --palette-1() {<br>result: if(<br>style(--theme: dark): black;<br>else: white;<br>);<br>@function --font-size() {<br>result: var(--size-1);
html {<br>--theme: light;<br>--size-1: 1rem;<br>html div {<br>--palette-1: --palette-1();<br>/* white */<br>font-size: --font-size();<br>/* 1rem */
.evaluation-scope {<br>--theme: dark;<br>--size-1: 20px;
--palette-1: --palette-1();<br>/* black */<br>font-size: --font-size();<br>/* 20px */
.evaluation-scope div {<br>--palette-1: --palette-1();<br>/* black */<br>font-size: --font-size();<br>/* 20px */<br>}Code language: CSS (css)
This is Awesome
Any component you build could have CSS functions associated with it. Those functions only ever get called from within the component, so their implementation can rely on a guaranteed evaluation scope from your component. 🤌 I love this.
CodePen Embed Fallback
Pause here for a moment to feel it if it’s not understood yet, because we’re about to build on this concept in an even cooler way!
You can Decouple Evaluation Scope from the DOM Entirely!
If you imagine defining a CSS function that’s only ever meant to be called from inside of another function , the potentially complex internals of the parent function becomes a pseudo-permanent evaluation scope for the child function.
A single parent function might call any number of related inner functions based on simple if(style()) switches:
@function --parent(--whichFn, --arg1, --arg2) {<br>/* execute a common set of operations */<br>/* for this portable evaluation scope */<br>/* that computes multiple shared vars */
--wow: calc(var(--arg1) * 2);<br>--pow: pow(var(--wow), var(--arg2));
/* Then branch execution of child fns */<br>result: if(<br>style(--whichFn: childA): --childA();<br>style(--whichFn: childB): --childB();<br>style(--whichFn: childC): --childC();<br>style(--whichFn: child64): --child64();<br>else: "Unknown Function";<br>);
@function --childA() {<br>/* do something with the complex vars */<br>/* from the portable evaluation scope */
result: calc(var(--wow) + var(--pow));
@function --childB() {<br>/* The same portable evaluation scope */<br>/* but computes a different series of */<br>/* operations unique to --childB() fn */<br>--many-more-steps: 9;
result: calc(<br>var(--pow) -<br>var(--wow) +<br>var(--many-more-steps)<br>);<br>}Code language: CSS (css)
This switch function "parent" could get a little heavy and gross for DX if you intend to expose it directly as an API endpoint in your library or component documentation. Each of the args might serve different purposes depending on the child, while still sharing a...