[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-css-property-registered-custom-properties":32,"code:css:true:j8hn0n":250,"code:css:true:1y7js3n":251,"code:css:true:i4lhuw":252,"code:css:true:vi2z0a":253,"code:css:true:a5dmvy":254},[4,20],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"id":33,"slug":34,"title":35,"subtitle":36,"excerpt":37,"coverUrl":38,"locale":13,"readingMinutes":39,"publishedAt":40,"viewCount":41,"likeCount":19,"commentCount":19,"author":42,"vertical":47,"topic":48,"tags":51,"_count":63,"playground":65,"body":67,"bodyMd":233,"seo":234,"translationGroupId":237,"thread":238,"assessments":240,"translations":246,"quiz":248},"019fe65f-f67a-7038-9c0d-874d6c8ece3c","css-property-registered-custom-properties","You can't transition a CSS variable. @property says otherwise.",null,"CSS custom properties are string substitution — the browser doesn't know a number from a color. Register one with @property and it gains a real type, an initial value, and the ability to animate.","\u002Fmedia\u002Fcovers\u002Fcss-property-registered-custom-properties.png",5,"2026-07-08T08:36:23.824Z",14,{"id":43,"name":44,"username":45,"avatarUrl":36,"headline":46},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":49,"name":50},"css","CSS",[52,54,57,60],{"slug":49,"name":53,"color":36},"Css",{"slug":55,"name":56,"color":36},"webdev","Webdev",{"slug":58,"name":59,"color":36},"frontend","Frontend",{"slug":61,"name":62,"color":36},"javascript","Javascript",{"assessments":64},1,{"slug":34,"title":66},"@property — give a CSS variable a type",{"blocks":68,"version":64},[69,73,78,81,84,88,91,94,97,101,104,108,111,114,117,120,123,126,129,133,136,140,143,146,149,153,156,159,163,166,169,172,176,179,182,185,188,191,194,197,200,203,206,209,212,215,218,221,224],{"id":70,"html":71,"type":72},"b1","\u003Cp>You&#39;ve probably written something like this:\u003C\u002Fp>","paragraph",{"id":74,"code":75,"type":76,"language":49,"highlight":77},"b2",".button {\n  --hue: 220;\n  background: hsl(var(--hue) 70% 50%);\n  transition: --hue 0.3s ease;\n}\n\n.button:hover {\n  --hue: 340;\n}","code",[],{"id":79,"html":80,"type":72},"b3","\u003Cp>You expected a smooth color shift. You got a snap cut.\u003C\u002Fp>",{"id":82,"html":83,"type":72},"b4","\u003Cp>That&#39;s not a browser bug. It&#39;s the result of one architectural decision baked into custom properties from the start: \u003Cstrong>CSS variables are text substitution, and the browser has no idea what type they hold.\u003C\u002Fstrong>\u003C\u002Fp>",{"id":85,"html":86,"text":86,"type":87,"level":31},"b5","What CSS custom properties actually are","heading",{"id":89,"html":90,"type":72},"b6","\u003Cp>When you write \u003Ccode>--hue: 220\u003C\u002Fcode>, the browser stores the string \u003Ccode>220\u003C\u002Fcode>. When it sees \u003Ccode>hsl(var(--hue) 70% 50%)\u003C\u002Fcode>, it does a find-and-replace: swap the token for the string, then parse the result as a color. No type information, no range checking. Just substitution.\u003C\u002Fp>",{"id":92,"html":93,"type":72},"b7","\u003Cp>That means the browser can&#39;t interpolate between \u003Ccode>--hue: 220\u003C\u002Fcode> and \u003Ccode>--hue: 340\u003C\u002Fcode> during a transition, because from its perspective, you&#39;re asking it to animate between two arbitrary strings. What does &quot;halfway between \u003Ccode>220\u003C\u002Fcode> and \u003Ccode>340\u003C\u002Fcode>&quot; mean when both are just text? The browser won&#39;t guess. It snaps.\u003C\u002Fp>",{"id":95,"html":96,"type":72},"b8","\u003Cp>This is also why \u003Ccode>calc(var(--scale) + 1)\u003C\u002Fcode> works fine — the substituted string parses as a valid calculation — but transitions involving custom properties silently fail without any error in the console to tell you why.\u003C\u002Fp>",{"id":98,"html":99,"text":100,"type":87,"level":31},"b9","\u003Ccode>@property\u003C\u002Fcode> gives a variable a type","@property gives a variable a type",{"id":102,"html":103,"type":72},"b10","\u003Cp>\u003Ccode>@property\u003C\u002Fcode> is a CSS at-rule that registers a custom property in the browser&#39;s type system. You declare three things: what type of value it holds, what its initial value is, and whether it inherits down the tree.\u003C\u002Fp>",{"id":105,"code":106,"type":76,"language":49,"highlight":107},"b11","@property --hue {\n  syntax: \"\u003Cnumber>\";\n  inherits: false;\n  initial-value: 220;\n}\n\n.button {\n  background: hsl(var(--hue) 70% 50%);\n  transition: --hue 0.3s ease;\n}\n\n.button:hover {\n  --hue: 340;\n}",[],{"id":109,"html":110,"type":72},"b12","\u003Cp>Now the browser knows \u003Ccode>--hue\u003C\u002Fcode> is a \u003Ccode>&lt;number&gt;\u003C\u002Fcode>. It can interpolate from 220 to 340 over 300ms. The color eases.\u003C\u002Fp>",{"id":112,"html":113,"type":72},"b13","\u003Cp>The change is one \u003Ccode>@property\u003C\u002Fcode> block at the top of your stylesheet. The rest of the CSS is identical to the version that was snapping.\u003C\u002Fp>",{"id":115,"html":116,"type":72},"b14","\u003C!-- playground:start -->",{"id":118,"html":119,"text":119,"type":87,"level":31},"b15","🎮 Try it yourself",{"id":121,"html":122,"type":72},"b16","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-08-css-property-registered-custom-properties\u002Fplayground\u002F\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":124,"html":125,"type":72},"b17","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":127,"html":128,"type":72},"b18","\u003C!-- playground:end -->",{"id":130,"html":131,"text":132,"type":87,"level":31},"b19","The types you&#39;ll actually reach for","The types you'll actually reach for",{"id":134,"html":135,"type":72},"b20","\u003Cp>\u003Ccode>syntax\u003C\u002Fcode> accepts CSS value type identifiers — the same vocabulary you&#39;d find in any property&#39;s value definition:\u003C\u002Fp>",{"id":137,"code":138,"type":76,"language":49,"highlight":139},"b21","syntax: \"\u003Cnumber>\";      \u002F* integer or float *\u002F\nsyntax: \"\u003Clength>\";      \u002F* 10px, 2rem, etc. *\u002F\nsyntax: \"\u003Ccolor>\";       \u002F* #abc, hsl(), oklch() *\u002F\nsyntax: \"\u003Cangle>\";       \u002F* 45deg, 0.5turn *\u002F\nsyntax: \"\u003Cpercentage>\";  \u002F* 0% to 100% *\u002F\nsyntax: \"*\";             \u002F* any value — same as unregistered *\u002F",[],{"id":141,"html":142,"type":72},"b22","\u003Cp>\u003Ccode>syntax: &quot;*&quot;\u003C\u002Fcode> is the escape hatch. Use it when you want \u003Ccode>initial-value\u003C\u002Fcode> or inheritance control without restricting what the property can hold.\u003C\u002Fp>",{"id":144,"html":145,"text":145,"type":87,"level":31},"b23","The gradient animation you gave up on",{"id":147,"html":148,"type":72},"b24","\u003Cp>The use case that put \u003Ccode>@property\u003C\u002Fcode> on most developers&#39; radar was animating the angle of a conic gradient — something that was impossible before, because \u003Ccode>background\u003C\u002Fcode> itself can&#39;t be transitioned (the value is too complex for the browser to interpolate). With a registered \u003Ccode>&lt;angle&gt;\u003C\u002Fcode> property driving it:\u003C\u002Fp>",{"id":150,"code":151,"type":76,"language":49,"highlight":152},"b25","@property --spin {\n  syntax: \"\u003Cangle>\";\n  inherits: false;\n  initial-value: 0deg;\n}\n\n.loader {\n  background: conic-gradient(\n    from var(--spin),\n    oklch(70% 0.2 220),\n    oklch(60% 0.3 300)\n  );\n  animation: rotate 1.5s linear infinite;\n}\n\n@keyframes rotate {\n  to { --spin: 360deg; }\n}",[],{"id":154,"html":155,"type":72},"b26","\u003Cp>The browser knows \u003Ccode>--spin\u003C\u002Fcode> is an \u003Ccode>&lt;angle&gt;\u003C\u002Fcode>, so \u003Ccode>@keyframes\u003C\u002Fcode> interpolates through the turn. The gradient rotates smoothly. No JavaScript, no wrapper element with \u003Ccode>transform: rotate()\u003C\u002Fcode> — just a typed variable doing the work a variable should do.\u003C\u002Fp>",{"id":157,"html":158,"type":72},"b27","\u003Cp>Before \u003Ccode>@property\u003C\u002Fcode>, this required either JavaScript to drive the animation frame by frame, or a \u003Ccode>rotate()\u003C\u002Fcode> hack on an absolutely-positioned pseudo-element. Neither felt right because neither was right.\u003C\u002Fp>",{"id":160,"html":161,"text":162,"type":87,"level":31},"b28","The hidden benefit: \u003Ccode>initial-value\u003C\u002Fcode>","The hidden benefit: initial-value",{"id":164,"html":165,"type":72},"b29","\u003Cp>Animated gradients are the headline, but \u003Ccode>initial-value\u003C\u002Fcode> is what earns \u003Ccode>@property\u003C\u002Fcode> a permanent place in how I structure stylesheets.\u003C\u002Fp>",{"id":167,"html":168,"type":72},"b30","\u003Cp>An unregistered custom property that&#39;s never declared returns an empty string. That empty string gets substituted wherever you use the property, which silently invalids the declaration. A missing \u003Ccode>--spacing-md\u003C\u002Fcode> isn&#39;t \u003Ccode>0\u003C\u002Fcode> — it&#39;s \u003Ccode>&quot;&quot;\u003C\u002Fcode>, and \u003Ccode>padding: &quot;&quot;\u003C\u002Fcode> is quietly thrown away by the parser. You won&#39;t get a console error. The layout just breaks in ways that take time to trace.\u003C\u002Fp>",{"id":170,"html":171,"type":72},"b31","\u003Cp>Register the property with a valid \u003Ccode>initial-value\u003C\u002Fcode> and the browser has a real fallback:\u003C\u002Fp>",{"id":173,"code":174,"type":76,"language":49,"highlight":175},"b32","@property --spacing-md {\n  syntax: \"\u003Clength>\";\n  inherits: true;\n  initial-value: 1rem;\n}\n\n.card {\n  padding: var(--spacing-md);  \u002F* 1rem if nothing sets it *\u002F\n}",[],{"id":177,"html":178,"type":72},"b33","\u003Cp>This matters most for design token systems where tokens flow through a large tree and you can&#39;t always guarantee the declaring element is an ancestor of the consuming element.\u003C\u002Fp>",{"id":180,"html":181,"text":181,"type":87,"level":31},"b34","Browser support, honestly",{"id":183,"html":184,"type":72},"b35","\u003Cp>\u003Ccode>@property\u003C\u002Fcode> shipped in Chrome 85 in late 2020, Firefox 128 in mid-2024, and Safari 16.4 in early 2023. It&#39;s Baseline 2024 — meaning cross-browser in any browser released in the last couple of years.\u003C\u002Fp>",{"id":186,"html":187,"type":72},"b36","\u003Cp>Browsers that don&#39;t support \u003Ccode>@property\u003C\u002Fcode> ignore the at-rule entirely (it&#39;s an unknown rule they skip over), and your custom property falls back to normal unregistered behavior. The property still works. Transitions still run — they just snap instead of easing. That&#39;s the progressive enhancement story: declare \u003Ccode>@property\u003C\u002Fcode>, get animated values in modern browsers, get the same static result in older ones.\u003C\u002Fp>",{"id":189,"html":190,"type":72},"b37","\u003Cp>No \u003Ccode>@supports\u003C\u002Fcode> guard required unless you&#39;re writing critical UI that would break on a snap cut.\u003C\u002Fp>",{"id":192,"html":193,"type":72},"b38","\u003C!-- quiz:start -->",{"id":195,"html":196,"text":196,"type":87,"level":31},"b39","🧠 Test yourself",{"id":198,"html":199,"type":72},"b40","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-08-css-property-registered-custom-properties\">Take the 6-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":201,"html":202,"type":72},"b41","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":204,"html":205,"type":72},"b42","\u003C!-- quiz:end -->",{"id":207,"html":208,"text":208,"type":87,"level":31},"b43","What just became possible",{"id":210,"html":211,"type":72},"b44","\u003Cp>Go back through your CSS and find every place you reached for JavaScript to drive an animation that was ultimately about changing a CSS value — a color shift, a progress bar, a spinning gradient, a morphing border radius. Ask: is this actually a typed variable animation in disguise?\u003C\u002Fp>",{"id":213,"html":214,"type":72},"b45","\u003Cp>The custom property was always there. The type is what was missing.\u003C\u002Fp>",{"id":216,"html":217,"type":72},"b46","\u003Cp>What animation have you been running in JavaScript that could be a registered CSS variable and a \u003Ccode>@keyframes\u003C\u002Fcode> block? Genuinely curious — because nine times out of ten when I&#39;ve asked this question, the JavaScript disappears.\u003C\u002Fp>",{"id":219,"type":220},"b47","divider",{"id":222,"html":223,"type":72},"b48","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":225,"type":226,"items":227,"ordered":18},"b49","list",[228,229,230,231,232],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","💼 \u003Cstrong>LinkedIn\u003C\u002Fstrong> — \u003Ca href=\"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F\">linkedin.com\u002Fin\u002Fparsa-jiravand\u003C\u002Fa>","✉️ \u003Cstrong>Email\u003C\u002Fstrong> (work &amp; contract inquiries): \u003Ca href=\"mailto:bestpractice2026@gmail.com\">bestpractice2026@gmail.com\u003C\u002Fa>","You've probably written something like this:\n\n```css\n.button {\n  --hue: 220;\n  background: hsl(var(--hue) 70% 50%);\n  transition: --hue 0.3s ease;\n}\n\n.button:hover {\n  --hue: 340;\n}\n```\n\nYou expected a smooth color shift. You got a snap cut.\n\nThat's not a browser bug. It's the result of one architectural decision baked into custom properties from the start: **CSS variables are text substitution, and the browser has no idea what type they hold.**\n\n## What CSS custom properties actually are\n\nWhen you write `--hue: 220`, the browser stores the string `220`. When it sees `hsl(var(--hue) 70% 50%)`, it does a find-and-replace: swap the token for the string, then parse the result as a color. No type information, no range checking. Just substitution.\n\nThat means the browser can't interpolate between `--hue: 220` and `--hue: 340` during a transition, because from its perspective, you're asking it to animate between two arbitrary strings. What does \"halfway between `220` and `340`\" mean when both are just text? The browser won't guess. It snaps.\n\nThis is also why `calc(var(--scale) + 1)` works fine — the substituted string parses as a valid calculation — but transitions involving custom properties silently fail without any error in the console to tell you why.\n\n## `@property` gives a variable a type\n\n`@property` is a CSS at-rule that registers a custom property in the browser's type system. You declare three things: what type of value it holds, what its initial value is, and whether it inherits down the tree.\n\n```css\n@property --hue {\n  syntax: \"\u003Cnumber>\";\n  inherits: false;\n  initial-value: 220;\n}\n\n.button {\n  background: hsl(var(--hue) 70% 50%);\n  transition: --hue 0.3s ease;\n}\n\n.button:hover {\n  --hue: 340;\n}\n```\n\nNow the browser knows `--hue` is a `\u003Cnumber>`. It can interpolate from 220 to 340 over 300ms. The color eases.\n\nThe change is one `@property` block at the top of your stylesheet. The rest of the CSS is identical to the version that was snapping.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fposts\u002F2026-07-08-css-property-registered-custom-properties\u002Fplayground\u002F)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The types you'll actually reach for\n\n`syntax` accepts CSS value type identifiers — the same vocabulary you'd find in any property's value definition:\n\n```css\nsyntax: \"\u003Cnumber>\";      \u002F* integer or float *\u002F\nsyntax: \"\u003Clength>\";      \u002F* 10px, 2rem, etc. *\u002F\nsyntax: \"\u003Ccolor>\";       \u002F* #abc, hsl(), oklch() *\u002F\nsyntax: \"\u003Cangle>\";       \u002F* 45deg, 0.5turn *\u002F\nsyntax: \"\u003Cpercentage>\";  \u002F* 0% to 100% *\u002F\nsyntax: \"*\";             \u002F* any value — same as unregistered *\u002F\n```\n\n`syntax: \"*\"` is the escape hatch. Use it when you want `initial-value` or inheritance control without restricting what the property can hold.\n\n## The gradient animation you gave up on\n\nThe use case that put `@property` on most developers' radar was animating the angle of a conic gradient — something that was impossible before, because `background` itself can't be transitioned (the value is too complex for the browser to interpolate). With a registered `\u003Cangle>` property driving it:\n\n```css\n@property --spin {\n  syntax: \"\u003Cangle>\";\n  inherits: false;\n  initial-value: 0deg;\n}\n\n.loader {\n  background: conic-gradient(\n    from var(--spin),\n    oklch(70% 0.2 220),\n    oklch(60% 0.3 300)\n  );\n  animation: rotate 1.5s linear infinite;\n}\n\n@keyframes rotate {\n  to { --spin: 360deg; }\n}\n```\n\nThe browser knows `--spin` is an `\u003Cangle>`, so `@keyframes` interpolates through the turn. The gradient rotates smoothly. No JavaScript, no wrapper element with `transform: rotate()` — just a typed variable doing the work a variable should do.\n\nBefore `@property`, this required either JavaScript to drive the animation frame by frame, or a `rotate()` hack on an absolutely-positioned pseudo-element. Neither felt right because neither was right.\n\n## The hidden benefit: `initial-value`\n\nAnimated gradients are the headline, but `initial-value` is what earns `@property` a permanent place in how I structure stylesheets.\n\nAn unregistered custom property that's never declared returns an empty string. That empty string gets substituted wherever you use the property, which silently invalids the declaration. A missing `--spacing-md` isn't `0` — it's `\"\"`, and `padding: \"\"` is quietly thrown away by the parser. You won't get a console error. The layout just breaks in ways that take time to trace.\n\nRegister the property with a valid `initial-value` and the browser has a real fallback:\n\n```css\n@property --spacing-md {\n  syntax: \"\u003Clength>\";\n  inherits: true;\n  initial-value: 1rem;\n}\n\n.card {\n  padding: var(--spacing-md);  \u002F* 1rem if nothing sets it *\u002F\n}\n```\n\nThis matters most for design token systems where tokens flow through a large tree and you can't always guarantee the declaring element is an ancestor of the consuming element.\n\n## Browser support, honestly\n\n`@property` shipped in Chrome 85 in late 2020, Firefox 128 in mid-2024, and Safari 16.4 in early 2023. It's Baseline 2024 — meaning cross-browser in any browser released in the last couple of years.\n\nBrowsers that don't support `@property` ignore the at-rule entirely (it's an unknown rule they skip over), and your custom property falls back to normal unregistered behavior. The property still works. Transitions still run — they just snap instead of easing. That's the progressive enhancement story: declare `@property`, get animated values in modern browsers, get the same static result in older ones.\n\nNo `@supports` guard required unless you're writing critical UI that would break on a snap cut.\n\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 6-question quiz →](https:\u002F\u002Fdaily-post-dev.netlify.app\u002Fquiz\u002Ftake.html?post=2026-07-08-css-property-registered-custom-properties)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n## What just became possible\n\nGo back through your CSS and find every place you reached for JavaScript to drive an animation that was ultimately about changing a CSS value — a color shift, a progress bar, a spinning gradient, a morphing border radius. Ask: is this actually a typed variable animation in disguise?\n\nThe custom property was always there. The type is what was missing.\n\nWhat animation have you been running in JavaScript that could be a registered CSS variable and a `@keyframes` block? Genuinely curious — because nine times out of ten when I've asked this question, the JavaScript disappears.\n\n---\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)\n- 💼 **LinkedIn** — [linkedin.com\u002Fin\u002Fparsa-jiravand](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fparsa-jiravand\u002F)\n- ✉️ **Email** (work & contract inquiries): [bestpractice2026@gmail.com](mailto:bestpractice2026@gmail.com)",{"title":35,"canonical":235,"description":236},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fcss-property-registered-custom-properties","CSS custom properties are string substitution — the browser doesn't know a number from a color. Register one with @property and it gains a real type, an initial value, and the abil","019fe65f-f67a-7038-9c0d-8b14527f5f58",{"id":239,"locked":18},"019fe65f-fdb5-7369-a3ad-9918a64f3799",[241],{"id":242,"slug":34,"title":243,"_count":244},"019fe776-2966-70db-b15c-78055d10d559","CSS @property: typed custom properties",{"questions":245},6,[247],{"locale":13,"slug":34},{"id":242,"slug":34,"title":243,"_count":249,"questionCount":245},{"questions":245},"\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  --\u003Cspan class=\"shj-var\">hue\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">220\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">hsl\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">hue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-num\">70\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan> \u003Cspan class=\"shj-num\">50\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">transition\u003C\u002Fspan>: --\u003Cspan class=\"shj-num\">hue\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0.3\u003C\u002Fspan>s \u003Cspan class=\"shj-num\">ease\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">.button\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:hover\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  --\u003Cspan class=\"shj-var\">hue\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">340\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">@property\u003C\u002Fspan> --\u003Cspan class=\"shj-kwd\">hue\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;number&gt;\"\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">inherits\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">false\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">initial-value\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">220\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">.button\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">hsl\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">hue\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-num\">70\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan> \u003Cspan class=\"shj-num\">50\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">transition\u003C\u002Fspan>: --\u003Cspan class=\"shj-num\">hue\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0.3\u003C\u002Fspan>s \u003Cspan class=\"shj-num\">ease\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">.button\u003C\u002Fspan>\u003Cspan class=\"shj-var\">:hover\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  --\u003Cspan class=\"shj-var\">hue\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">340\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;number&gt;\"\u003C\u002Fspan>;      \u003Cspan class=\"shj-cmnt\">\u002F* integer or float *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;length&gt;\"\u003C\u002Fspan>;      \u003Cspan class=\"shj-cmnt\">\u002F* 10px, 2rem, etc. *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;color&gt;\"\u003C\u002Fspan>;       \u003Cspan class=\"shj-cmnt\">\u002F* #abc, hsl(), oklch() *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;angle&gt;\"\u003C\u002Fspan>;       \u003Cspan class=\"shj-cmnt\">\u002F* 45deg, 0.5turn *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;percentage&gt;\"\u003C\u002Fspan>;  \u003Cspan class=\"shj-cmnt\">\u002F* 0% to 100% *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"*\"\u003C\u002Fspan>;             \u003Cspan class=\"shj-cmnt\">\u002F* any value — same as unregistered *\u002F\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003Cdiv>16\u003C\u002Fdiv>\u003Cdiv>17\u003C\u002Fdiv>\u003Cdiv>18\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">@property\u003C\u002Fspan> --\u003Cspan class=\"shj-kwd\">spin\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;angle&gt;\"\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">inherits\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">false\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">initial-value\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>deg;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">.loader\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">background\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">conic-gradient\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\n    \u003Cspan class=\"shj-num\">from\u003C\u002Fspan> \u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">spin\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>,\n    \u003Cspan class=\"shj-func\">oklch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">70\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0.2\u003C\u002Fspan> \u003Cspan class=\"shj-num\">220\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>,\n    \u003Cspan class=\"shj-func\">oklch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">60\u003C\u002Fspan>\u003Cspan class=\"shj-var\">%\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0.3\u003C\u002Fspan> \u003Cspan class=\"shj-num\">300\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">animation\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">rotate\u003C\u002Fspan> \u003Cspan class=\"shj-num\">1.5\u003C\u002Fspan>s \u003Cspan class=\"shj-num\">linear\u003C\u002Fspan> \u003Cspan class=\"shj-num\">infinite\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-kwd\">@keyframes\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">rotate\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">to\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan> --\u003Cspan class=\"shj-var\">spin\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">360\u003C\u002Fspan>deg; \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-css shj-multiline\" data-lang=\"css\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">@property\u003C\u002Fspan> --\u003Cspan class=\"shj-kwd\">spacing-md\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">syntax\u003C\u002Fspan>: \u003Cspan class=\"shj-str\">\"&lt;length&gt;\"\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">inherits\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">true\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-var\">initial-value\u003C\u002Fspan>: \u003Cspan class=\"shj-num\">1\u003C\u002Fspan>\u003Cspan class=\"shj-var\">rem\u003C\u002Fspan>\u003Cspan class=\"shj-num\">\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-var\">.card\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-var\">padding\u003C\u002Fspan>: \u003Cspan class=\"shj-func\">var\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>--\u003Cspan class=\"shj-num\">spacing-md\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;  \u003Cspan class=\"shj-cmnt\">\u002F* 1rem if nothing sets it *\u002F\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>"]