{"id":9932,"date":"2026-06-16T13:23:58","date_gmt":"2026-06-16T18:23:58","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9932"},"modified":"2026-06-16T14:29:03","modified_gmt":"2026-06-16T19:29:03","slug":"the-scope-of-css-function","status":"publish","type":"post","link":"https:\/\/master.dev\/blog\/the-scope-of-css-function\/","title":{"rendered":"The Scope of CSS @function"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">We&#8217;re going to walk through some advanced patterns for using <code>@function<\/code> in CSS in this article that help you deliver awesome DX to your component or library users. You&#8217;ll need <a href=\"https:\/\/frontendmasters.com\/blog\/the-fundamentals-and-dev-experience-of-css-function\/\">an understanding of CSS function foundations and limitations<\/a> to follow along with the gold here.<\/p>\n\n\n<div class=\"box article-series\">\n  <header>\n    <h3 class=\"article-series-header\">Article Series<\/h3>\n  <\/header>\n  <div class=\"box-content\">\n            <ol>\n                      <li>\n              <a href=\"https:\/\/master.dev\/blog\/the-fundamentals-and-dev-experience-of-css-function\/\">The Fundamentals and Dev Experience of CSS @function<\/a>\n            <\/li>\n                      <li>\n              <a href=\"https:\/\/master.dev\/blog\/the-scope-of-css-function\/\">The Scope of CSS @function<\/a>\n            <\/li>\n                  <\/ol>\n        <\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">The Variable Scope of Custom CSS Functions<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Variable scope in custom CSS @ functions is really fascinating. In normal CSS, <code>--vars<\/code> inherit from parent elements down to child elements. The children can freely use the inherited <code>--vars<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">html<\/span> {\n  <span class=\"hljs-attribute\">--theme<\/span>: light;\n  <span class=\"hljs-attribute\">--size-1<\/span>: <span class=\"hljs-number\">1rem<\/span>;\n}\n<span class=\"hljs-selector-tag\">html<\/span> <span class=\"hljs-selector-tag\">div<\/span> {\n  <span class=\"hljs-attribute\">--palette-1<\/span>: <span class=\"hljs-built_in\">if<\/span>(\n    style(--theme: dark): black;\n    <span class=\"hljs-attribute\">else<\/span>: white;\n  );\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-built_in\">var<\/span>(--size-<span class=\"hljs-number\">1<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Similarly, in JavaScript, a function defined inside of a context <em>inherits<\/em> all of the variables from the context it&#8217;s defined in, to be used freely within the function. Colloquially in JavaScript, this context that variables are inherited from is called the <strong>closure scope<\/strong>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> html, div\n\nhtml = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> theme = <span class=\"hljs-string\">\"light\"<\/span>\n  <span class=\"hljs-keyword\">const<\/span> size1 = <span class=\"hljs-string\">\"1rem\"<\/span>\n\n  div = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> palette1 = theme === <span class=\"hljs-string\">\"dark\"<\/span> ? <span class=\"hljs-string\">\"black\"<\/span> : <span class=\"hljs-string\">\"white\"<\/span>\n    <span class=\"hljs-keyword\">const<\/span> fontSize = size1\n    <span class=\"hljs-keyword\">return<\/span> { palette1, fontSize }\n  }\n\n  <span class=\"hljs-keyword\">return<\/span> { theme, size1 }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">Traditional CSS <code>var()<\/code> usages relies heavily on patterns and expectations from your DOM structure.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">CSS Functions Have a Fascinating Superpower with Scope<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">They experience an inherited sort-of-closure scope <em>from wherever they&#8217;re called<\/em>. This exposes their internal work to all of the variables in any context they&#8217;re called from, <em>as if they were a child element in the DOM and inherited them<\/em>!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In JavaScript, it&#8217;s as if the definition for the child function was kept as a string and passed through <code>eval()<\/code> wherever it&#8217;s used.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">An <strong>evaluation scope<\/strong>, so to speak.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\"><span class=\"hljs-keyword\">let<\/span> html, div\n\n<span class=\"hljs-keyword\">const<\/span> fn = <span class=\"hljs-string\">`() =&gt; {\n  const palette1 = theme === \"dark\" ? \"black\" : \"white\"\n  const fontSize = size1\n  return { palette1, fontSize }\n}`<\/span>\n\nhtml = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> theme = <span class=\"hljs-string\">\"light\"<\/span>\n  <span class=\"hljs-keyword\">const<\/span> size1 = <span class=\"hljs-string\">\"1rem\"<\/span>\n\n  div = <span class=\"hljs-built_in\">eval<\/span>(fn)\n\n  <span class=\"hljs-keyword\">return<\/span> { theme, size1 }\n}\n\nhtml().size1 === div().fontSize\n<span class=\"hljs-comment\">\/\/ true<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">In CSS:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@function<\/span> --palette-<span class=\"hljs-number\">1<\/span>() {\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">if<\/span>(\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--theme<\/span>: <span class=\"hljs-selector-tag\">dark<\/span>): <span class=\"hljs-selector-tag\">black<\/span>;\n    <span class=\"hljs-selector-tag\">else<\/span>: <span class=\"hljs-selector-tag\">white<\/span>;\n  );\n}\n<span class=\"hljs-keyword\">@function<\/span> --font-size() {\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--size-1<\/span>);\n}\n\n<span class=\"hljs-selector-tag\">html<\/span> {\n  <span class=\"hljs-attribute\">--theme<\/span>: light;\n  <span class=\"hljs-attribute\">--size-1<\/span>: <span class=\"hljs-number\">1rem<\/span>;\n}\n<span class=\"hljs-selector-tag\">html<\/span> <span class=\"hljs-selector-tag\">div<\/span> {\n  <span class=\"hljs-attribute\">--palette-1<\/span>: <span class=\"hljs-built_in\">--palette-1<\/span>();\n  <span class=\"hljs-comment\">\/* white *\/<\/span>\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-built_in\">--font-size<\/span>();\n  <span class=\"hljs-comment\">\/* 1rem *\/<\/span>\n}\n\n<span class=\"hljs-selector-class\">.evaluation-scope<\/span> {\n  <span class=\"hljs-attribute\">--theme<\/span>: dark;\n  <span class=\"hljs-attribute\">--size-1<\/span>: <span class=\"hljs-number\">20px<\/span>;\n\n  <span class=\"hljs-attribute\">--palette-1<\/span>: <span class=\"hljs-built_in\">--palette-1<\/span>();\n  <span class=\"hljs-comment\">\/* black *\/<\/span>\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-built_in\">--font-size<\/span>();\n  <span class=\"hljs-comment\">\/* 20px *\/<\/span>\n}\n\n<span class=\"hljs-selector-class\">.evaluation-scope<\/span> <span class=\"hljs-selector-tag\">div<\/span> {\n  <span class=\"hljs-attribute\">--palette-1<\/span>: <span class=\"hljs-built_in\">--palette-1<\/span>();\n  <span class=\"hljs-comment\">\/* black *\/<\/span>\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-built_in\">--font-size<\/span>();\n  <span class=\"hljs-comment\">\/* 20px *\/<\/span>\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">This is Awesome<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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 <strong>evaluation scope<\/strong> from your component. \ud83e\udd0c <strong>I love this.<\/strong><\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_PwbEOJr\/23ab4568b799fed4ca155b13b2f0055d\" src=\"\/\/codepen.io\/anon\/embed\/PwbEOJr\/23ab4568b799fed4ca155b13b2f0055d?height=450&amp;theme-id=1&amp;slug-hash=PwbEOJr\/23ab4568b799fed4ca155b13b2f0055d&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed PwbEOJr\/23ab4568b799fed4ca155b13b2f0055d\" title=\"CodePen Embed PwbEOJr\/23ab4568b799fed4ca155b13b2f0055d\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Pause here for a moment to feel it if it&#8217;s not understood yet, because we&#8217;re about to build on this concept in an even cooler way!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">You can Decouple Evaluation Scope from the DOM Entirely!<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you imagine defining a CSS function that&#8217;s <strong>only ever meant to be called from inside of another function<\/strong>, the potentially complex internals of the parent function becomes a pseudo-permanent <strong>evaluation scope<\/strong> for the child function.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A single parent function might call any number of related inner functions based on simple <code>if(style())<\/code> switches:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@function<\/span> --parent(--whichFn, --arg1, --arg2) {\n  <span class=\"hljs-comment\">\/* execute a common set of operations *\/<\/span>\n  <span class=\"hljs-comment\">\/* for this portable evaluation scope *\/<\/span>\n  <span class=\"hljs-comment\">\/* that computes multiple shared vars *\/<\/span>\n\n  <span class=\"hljs-selector-tag\">--wow<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--arg1<\/span>) * 2);\n  <span class=\"hljs-selector-tag\">--pow<\/span>: <span class=\"hljs-selector-tag\">pow<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--wow<\/span>), <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--arg2<\/span>));\n\n  <span class=\"hljs-comment\">\/* Then branch execution of child fns *\/<\/span>\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">if<\/span>(\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--whichFn<\/span>: <span class=\"hljs-selector-tag\">childA<\/span>): <span class=\"hljs-selector-tag\">--childA<\/span>();\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--whichFn<\/span>: <span class=\"hljs-selector-tag\">childB<\/span>): <span class=\"hljs-selector-tag\">--childB<\/span>();\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--whichFn<\/span>: <span class=\"hljs-selector-tag\">childC<\/span>): <span class=\"hljs-selector-tag\">--childC<\/span>();\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--whichFn<\/span>: <span class=\"hljs-selector-tag\">child64<\/span>): <span class=\"hljs-selector-tag\">--child64<\/span>();\n    <span class=\"hljs-selector-tag\">else<\/span>: \"<span class=\"hljs-selector-tag\">Unknown<\/span> <span class=\"hljs-selector-tag\">Function<\/span>\";\n  );\n}\n\n<span class=\"hljs-keyword\">@function<\/span> --childA() {\n  <span class=\"hljs-comment\">\/* do something with the complex vars *\/<\/span>\n  <span class=\"hljs-comment\">\/* from the portable evaluation scope *\/<\/span>\n\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--wow<\/span>) + <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--pow<\/span>));\n}\n\n<span class=\"hljs-keyword\">@function<\/span> --childB() {\n  <span class=\"hljs-comment\">\/* The same portable evaluation scope *\/<\/span>\n  <span class=\"hljs-comment\">\/* but computes a different series of *\/<\/span>\n  <span class=\"hljs-comment\">\/* operations unique to --childB() fn *\/<\/span>\n  <span class=\"hljs-selector-tag\">--many-more-steps<\/span>: 9;\n\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">calc<\/span>(\n    <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--pow<\/span>) <span class=\"hljs-selector-tag\">-<\/span>\n    <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--wow<\/span>) +\n    <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--many-more-steps<\/span>)\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">This switch function &#8220;parent&#8221; 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 series of common steps.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>But<\/strong> that&#8217;s only a concern if you stop there! Take it one tiny step further:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Create <code>outerA<\/code> and <code>outerB<\/code> functions as a sort of <a href=\"https:\/\/www.digitalocean.com\/community\/tutorials\/javascript-functional-programming-explained-partial-application-and-currying#conclusion\">Partial Application<\/a> (or taken all the way to pseudo curried functions if needed).<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@function<\/span> --outerA(--arg1, --arg2: <span class=\"hljs-number\">10<\/span>) {\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">--parent<\/span>(<span class=\"hljs-selector-tag\">childA<\/span>, <span class=\"hljs-selector-tag\">--arg1<\/span>, <span class=\"hljs-selector-tag\">--arg2<\/span>);\n}\n<span class=\"hljs-keyword\">@function<\/span> --outerB(--arg1, --arg2: <span class=\"hljs-number\">1<\/span>) {\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">--parent<\/span>(<span class=\"hljs-selector-tag\">childB<\/span>, <span class=\"hljs-selector-tag\">--arg1<\/span>, <span class=\"hljs-selector-tag\">--arg2<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">These outer functions become your API to use throughout the code base instead of using the switch function directly.<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">You keep the complexity of a switch board API hidden, you keep the child functions relying on it hidden (because they rely on the <strong>portable evaluation scope<\/strong> and can&#8217;t be called from anywhere else), and your final delivered DX is only using the individual outer functions. It&#8217;s \ud83e\udd0c as close to trivial as you can get while packing in serious complexity.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><span style=\"box-sizing: border-box; margin: 0px; padding: 0px;\">In short, you create a&nbsp;<strong>Portable Evaluation Scope<\/strong>&nbsp;for internal use, and it&#8217;s completely hidden from your users&#8217; DX.<\/span> \ud83d\udc7d<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Real World Use Case<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code><a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/tree\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\">@propjockey\/doubledash.css<\/a><\/code> has <a href=\"https:\/\/propjockey.github.io\/doubledash.css\/#int16-logic\">bitwise operations on 16bit integers<\/a>. Internally, there is a single &#8220;bitwise&#8221; switch function:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@function<\/span> --dd-bitwise(--dd-int16-a, --dd-int16-op, --dd-int16<span class=\"hljs-attribute\">-b:<\/span> <span class=\"hljs-number\">0<\/span>, --dd-int16<span class=\"hljs-attribute\">-set-bit-to:<\/span> <span class=\"hljs-number\">0<\/span>) {\n  <span class=\"hljs-selector-tag\">--_dd-a-sign<\/span>: <span class=\"hljs-selector-tag\">sign<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-a<\/span>));\n  <span class=\"hljs-selector-tag\">--_dd-a-down-to-int<\/span>: <span class=\"hljs-selector-tag\">clamp<\/span>(\n    0,\n    <span class=\"hljs-selector-tag\">round<\/span>(<span class=\"hljs-selector-tag\">down<\/span>, <span class=\"hljs-selector-tag\">abs<\/span>(<span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-a<\/span>)), 1),\n    <span class=\"hljs-selector-tag\">pow<\/span>(2, 16) <span class=\"hljs-selector-tag\">-<\/span> 1\n  );\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">if<\/span>(\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-op<\/span>: <span class=\"hljs-selector-tag\">right<\/span>): <span class=\"hljs-selector-tag\">calc<\/span>(\n      <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--_dd-a-sign<\/span>) * <span class=\"hljs-selector-tag\">clamp<\/span>(\n        0,\n        <span class=\"hljs-selector-tag\">round<\/span>(<span class=\"hljs-selector-tag\">down<\/span>, <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--_dd-a-down-to-int<\/span>) \/ <span class=\"hljs-selector-tag\">pow<\/span>(2, <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-b<\/span>)), 1),\n        <span class=\"hljs-selector-tag\">pow<\/span>(2, 16) <span class=\"hljs-selector-tag\">-<\/span> 1\n      )\n    ); <span class=\"hljs-selector-tag\">else<\/span>: <span class=\"hljs-selector-tag\">--_dd-bw-split-a<\/span>(\n      <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-a<\/span>),\n      <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-op<\/span>),\n      <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-b<\/span>),\n      <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--dd-int16-set-bit-to<\/span>)\n    );\n  );\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">If the operation is a <code>right shift<\/code>, it just uses division and powers to return the result, for all other operations, it adds a <strong>portable exposure scope<\/strong> layer by calling another internal <code>--_dd-bw-split-a()<\/code> which <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/logic\/int16\/bitwise.css#L290\">splits the int16-a argument into 16 individual bits<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Operations like <a href=\"https:\/\/propjockey.github.io\/doubledash.css\/#not-int16\">bitwise &#8220;NOT&#8221;<\/a> flip each of the bits, <code>calc()<\/code> it back into a new int, and that&#8217;s the result.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Other operations like <a href=\"https:\/\/propjockey.github.io\/doubledash.css\/#xor-int16\">bitwise &#8220;XOR&#8221;<\/a> call a third internal function, adding another <strong>portable exposure scope<\/strong> layer to also <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/logic\/int16\/bitwise.css#L228\">split the int16-b argument into 16 more individual bits<\/a>, and each bit from both evaluation scopes are <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/logic\/int16\/bitwise.css#L183\">xor&#8217;d together for the final result<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This pattern allows encapsulation and segregation of functionality, the split-b function <em>only splits the argument into bits,<\/em> and then <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/logic\/int16\/bitwise.css#L287\">it calls yet another function<\/a> to handle <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/logic\/int16\/bitwise.css#L102\">the actual switch board<\/a> from the original internal call to <code>bitwise<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/propjockey.github.io\/doubledash.css\/#xor-int16\">exposed API in doubledash&#8217;s documentation<\/a> has a <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/logic\/int16\/xor-int16.css\">bare minimum implementation<\/a> and it&#8217;s the only thing dev users of the library ever see.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_EaNoRqZ\/bc606014fa5b738b2edbfe928494a6ae\" src=\"\/\/codepen.io\/anon\/embed\/EaNoRqZ\/bc606014fa5b738b2edbfe928494a6ae?height=450&amp;theme-id=1&amp;slug-hash=EaNoRqZ\/bc606014fa5b738b2edbfe928494a6ae&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed EaNoRqZ\/bc606014fa5b738b2edbfe928494a6ae\" title=\"CodePen Embed EaNoRqZ\/bc606014fa5b738b2edbfe928494a6ae\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Portable Evaluation Scope <em>as a feature<\/em><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You may want to actually provide a complex <strong>portable evaluation scope<\/strong> for your developer users to take advantage of with their own functionality extended from it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This necessarily exposes the switch function to your dev user so standardized arguments across the shared switch function set are more important.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In JavaScript terms, you&#8217;d pass a reference to your custom function (as a string) into the library function and it would <code>eval(yourFnStr)<\/code> in place. This is something you <strong>wouldn&#8217;t do<\/strong> in JavaScript because of security concerns, but evaluation scope is baked into CSS <em>without security concerns<\/em> so we get to have all the fun we want!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">However, in CSS we <strong>can&#8217;t<\/strong> <em>currently<\/em> pass functions by reference; Just like vars, there is no dynamic\/interpolated references allowed yet, so <strong>this doesn&#8217;t work<\/strong>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@function<\/span> --dev-user-fn() {\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--use-portable-exposure-scope<\/span>) \" <span class=\"hljs-selector-tag\">world<\/span>!\";\n}\n\n<span class=\"hljs-keyword\">@function<\/span> --library-fn(--fn) {\n  <span class=\"hljs-selector-tag\">--use-portable-exposure-scope<\/span>: \"<span class=\"hljs-selector-tag\">Hello<\/span>\";\n  <span class=\"hljs-selector-tag\">result<\/span>: <span class=\"hljs-selector-tag\">--fn<\/span>();\n}\n\n<span class=\"hljs-selector-tag\">body<\/span><span class=\"hljs-selector-pseudo\">::before<\/span> {\n  <span class=\"hljs-attribute\">content<\/span>: <span class=\"hljs-built_in\">--library-fn<\/span>(--dev-user-fn);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s another unfortunate gotcha since it would be incredibly useful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So to do this anyway because we ignore &#8220;can&#8217;t&#8221; as a philosophy, as the library or component author, we just set up the switch board function <em>to call functions that aren&#8217;t defined<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your developer user defines them. You document what your <strong>portable evaluation scope<\/strong> provides for them to use, and you&#8217;re both golden. \ud83c\udf89<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\">Gotcha: You do NOT want to simply pass arguments to your functions on the switch board because, as <a href=\"https:\/\/frontendmasters.com\/blog\/the-fundamentals-and-dev-experience-of-css-function\/#function-arguments\">detailed in the previous article<\/a>, if you call a function that doesn&#8217;t have each of those arguments defined, the call will fail without any way to debug it other than just knowing that&#8217;s why. This is bad DX. <strong>Portable evaluation scope<\/strong> instead allows your users to quickly define the function in minimal syntax, without parameters, and just use whichever variables they need.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In short, your dev user calls your switch board containing the <strong>portable evaluation scope<\/strong>, pass in the identifier, and it executes the corresponding function they&#8217;ve defined.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Real world use case<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The <a href=\"https:\/\/propjockey.github.io\/doubledash.css\/#loop\">world&#8217;s first 100% CSS loops<\/a> in doubledash do exactly this.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It exposes a <code>--dd-loop()<\/code> function for dev users which takes a library-defined switch key like <code>--dd-loop-id-7<\/code> as one of the arguments. Internally it does all the complexity to make it possible to iterate in a static cascading language, then ultimately <a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/repeat\/loop.css#L75C28-L75C42\">checks the switch key and executes the corresponding dev-user-provided function<\/a> that defines the body of their loop function.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since there is no way to dynamically pass functions yet, the library provides 64 unused function slots in the switch so dev users can implement up to 64 global definitions for unique use cases of loops. The <strong>portable evaluations scope<\/strong> of the loop provides the current iteration index and current \u201cx\u201d value, among other details.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">CSS does not compute what isn&#8217;t used.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_RNoWBBK\" src=\"\/\/codepen.io\/anon\/embed\/RNoWBBK?height=450&amp;theme-id=1&amp;slug-hash=RNoWBBK&amp;default-tab=css,result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed RNoWBBK\" title=\"CodePen Embed RNoWBBK\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">As shown in the Pen, the switch board API function comes with a bonus. Dev users can set a variable like<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>--alias: --dd-loop-id-0;<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">so when they call your switch board to use the <strong>portable evaluation scope<\/strong>, they pass in their <code>var(--alias)<\/code> as the key and your switch board knows what to do with it automatically. This helps keep things organized if the dev user plans on using more than one or two loops.<\/p>\n\n\n\n<p class=\"learn-more wp-block-paragraph\"><strong>DX Gotcha:<\/strong> you can&#8217;t provide variables to override the alias and let them pass the alias directly as an identifier because <code>if(style())<\/code> can&#8217;t check if <code>--arg === var(--alias-1)<\/code>, it must (currently) only check hardcoded values. Unfortunate DX but at least the <code>var(--alias)<\/code> option is a step in the right direction.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Wrapping the Portable Evaluation Scope<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Your dev users can wrap the call to your switch board with a custom function of their own that defines parameters however they see fit, and those parameters become part of the <strong>evaluation scope<\/strong> of the child function they defined.<\/p>\n\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_ZYBQzzO\" src=\"\/\/codepen.io\/anon\/embed\/ZYBQzzO?height=450&amp;theme-id=1&amp;slug-hash=ZYBQzzO&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed ZYBQzzO\" title=\"CodePen Embed ZYBQzzO\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">In this 100% CSS Pen, which is <a href=\"https:\/\/codepen.io\/thebabydino\/pen\/AvqmXO?editors=1100\">originally PostCSS artwork by Ana Tudor<\/a>, the dev user&#8217;s <code>--particles()<\/code> function defines arguments, then calls doubledash&#8217;s library <code>loop<\/code> function, which exposes the arguments to the <code>--dd-loop-id-0()<\/code> function body as part of its <strong>portable evaluation scope<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This DX feels similar to defining a C header file separately from the implementation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Just like the bitwise functions where we provided individual OuterA and OuterB functions, your switch board <strong>portable evaluation scope<\/strong> could be wrapped by the dev user multiple different ways as well. For example, <code>--dot-particles()<\/code> and <code>--star-particles()<\/code> could use the same underlying library function with different arguments defaulted.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It&#8217;s almost function overloading but the final functions need unique names.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Actually Overloading Signatures<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you define generic variable arguments like <code>--arg1<\/code> and <code>--arg2<\/code> and make them optional by providing default values, in your documentation, you can lie about what the arguments are called in different signatures.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, the <a href=\"https:\/\/propjockey.github.io\/doubledash.css\/#repeat\">simple <code>--dd-repeat()<\/code> function in doubledash<\/a> does this when it makes the middle parameter optional as API documentation, but technically it\u2019s the 3rd argument that\u2019s optional.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/propjockey\/doubledash.css\/blob\/b153f2d397ba83026e5cc997c5e77b7b7761be7c\/functions\/repeat\/repeat.css#L7-L13\">Internally<\/a>, it resolves the arguments to reasonable variable names for use deeper in the function.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@function<\/span> --dd-repeat(--dd-total, --_dd-arg2, --_dd-arg3: initial) {\n  <span class=\"hljs-selector-tag\">--dd-data<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--_dd-arg3<\/span>, <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--_dd-arg2<\/span>));\n  <span class=\"hljs-selector-tag\">--dd-joiner<\/span>: <span class=\"hljs-selector-tag\">if<\/span>(<span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--_dd-arg3<\/span>): <span class=\"hljs-selector-tag\">if<\/span>(\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--_dd-arg2<\/span>: <span class=\"hljs-selector-tag\">none<\/span>): ;\n    <span class=\"hljs-selector-tag\">style<\/span>(<span class=\"hljs-selector-tag\">--_dd-arg2<\/span>: <span class=\"hljs-selector-tag\">comma<\/span>): ,;\n    <span class=\"hljs-selector-tag\">else<\/span>: <span class=\"hljs-selector-tag\">var<\/span>(<span class=\"hljs-selector-tag\">--_dd-arg2<\/span>, );\n  ); <span class=\"hljs-selector-tag\">else<\/span>: ;);\n\n  ...\n\n  <span class=\"hljs-selector-tag\">result<\/span>: ...;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p class=\"wp-block-paragraph\">As <a href=\"https:\/\/frontendmasters.com\/blog\/the-fundamentals-and-dev-experience-of-css-function\/#function-arguments:~:text=initial%20value%20is-,particularly%20useful,-as%20a%20default\">suggested in the previous article<\/a>, using <code>initial<\/code> as the default argument value makes this specific case fairly easy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Data uses argument 3, unless it&#8217;s not defined, then argument 2 is data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Similarly, joiner uses <code>if(style())<\/code> to check if argument 3 is defined and defaults to an inert <code>&lt;empty&gt;<\/code> whitespace if it&#8217;s not.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The End<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Evaluation scope is a mighty powerful tool for custom CSS functions. Even though the <a href=\"https:\/\/frontendmasters.com\/blog\/the-fundamentals-and-dev-experience-of-css-function\/\">foundational DX of custom CSS functions<\/a> is <em>currently<\/em> overflowing with brutal caveats, this evaluation scope is absolutely a 10-pin strike worth exploring.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the end, if you&#8217;re shipping DX that&#8217;s better than the underlying technology that you&#8217;ve bent to your will, it is a satisfying win.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your documentation stays on the surface and says to your users:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Everything I can do, now we can both do without needing to do what I had to do because now this does all of that for both us, and neither of us have to worry about any of it. \ud83d\ude01<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">In other words, <a href=\"https:\/\/github.com\/propjockey\/propjockey.io\/blame\/main\/src\/components\/Jane.astro#L76\">I make things that help people make things<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Please do reach out if you enjoyed this, have questions, or want to show off what you&#8217;ve done: <a href=\"https:\/\/bsky.app\/profile\/janeori.propjockey.io\">I invite Open Contact \ud83d\udc9a\ud83d\udc7d<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are some real advantages to variable scope and evaluation scope that you get with @function in CSS.<\/p>\n","protected":false},"author":50,"featured_media":10051,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[1],"tags":[395,7,396],"class_list":["post-9932","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-function","tag-css","tag-if"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/master.dev\/blog\/wp-content\/uploads\/2026\/06\/function-dots.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/9932","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/users\/50"}],"replies":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/comments?post=9932"}],"version-history":[{"count":12,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/9932\/revisions"}],"predecessor-version":[{"id":10129,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/posts\/9932\/revisions\/10129"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media\/10051"}],"wp:attachment":[{"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/media?parent=9932"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/categories?post=9932"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/master.dev\/blog\/wp-json\/wp\/v2\/tags?post=9932"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}