[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-css-property-registered-custom-properties":32,"quiz-article-css-property-registered-custom-properties":48},[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,"kind":35,"title":36,"description":37,"config":38,"verticalId":5,"vertical":43,"course":40,"_count":44,"access":45,"attempts":47,"questionCount":39},"019fe776-2966-70db-b15c-78055d10d559","css-property-registered-custom-properties","PRACTICE_QUIZ","CSS @property: typed custom properties","Why an unregistered CSS variable snaps instead of easing, and how @property gives it a real type, an initial value, and the ability to animate. Covers syntax, initial-value, and the gradient-angle trick.",{"questionCount":39,"timeLimitSec":40,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":41,"maxAttempts":40,"revealAnswers":42,"allowFlagging":18,"allowBacktracking":17},6,null,70,"IMMEDIATE",{"slug":6,"name":7},{"questions":39},{"allowed":17,"reason":46},"FREE",[],{"id":49,"slug":34,"title":50,"subtitle":40,"excerpt":51,"coverUrl":52,"locale":13,"readingMinutes":53,"publishedAt":54,"viewCount":55,"likeCount":19,"commentCount":19,"author":56,"vertical":61,"topic":62,"tags":65,"_count":77,"playground":79,"body":81,"bodyMd":247,"seo":248,"translationGroupId":251,"thread":252,"assessments":254,"translations":257,"quiz":259},"019fe65f-f67a-7038-9c0d-874d6c8ece3c","You can't transition a CSS variable. @property says otherwise.","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",13,{"id":57,"name":58,"username":59,"avatarUrl":40,"headline":60},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":63,"name":64},"css","CSS",[66,68,71,74],{"slug":63,"name":67,"color":40},"Css",{"slug":69,"name":70,"color":40},"webdev","Webdev",{"slug":72,"name":73,"color":40},"frontend","Frontend",{"slug":75,"name":76,"color":40},"javascript","Javascript",{"assessments":78},1,{"slug":34,"title":80},"@property — give a CSS variable a type",{"blocks":82,"version":78},[83,87,92,95,98,102,105,108,111,115,118,122,125,128,131,134,137,140,143,147,150,154,157,160,163,167,170,173,177,180,183,186,190,193,196,199,202,205,208,211,214,217,220,223,226,229,232,235,238],{"id":84,"html":85,"type":86},"b1","\u003Cp>You&#39;ve probably written something like this:\u003C\u002Fp>","paragraph",{"id":88,"code":89,"type":90,"language":63,"highlight":91},"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":93,"html":94,"type":86},"b3","\u003Cp>You expected a smooth color shift. You got a snap cut.\u003C\u002Fp>",{"id":96,"html":97,"type":86},"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":99,"html":100,"text":100,"type":101,"level":31},"b5","What CSS custom properties actually are","heading",{"id":103,"html":104,"type":86},"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":106,"html":107,"type":86},"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":109,"html":110,"type":86},"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":112,"html":113,"text":114,"type":101,"level":31},"b9","\u003Ccode>@property\u003C\u002Fcode> gives a variable a type","@property gives a variable a type",{"id":116,"html":117,"type":86},"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":119,"code":120,"type":90,"language":63,"highlight":121},"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":123,"html":124,"type":86},"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":126,"html":127,"type":86},"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":129,"html":130,"type":86},"b14","\u003C!-- playground:start -->",{"id":132,"html":133,"text":133,"type":101,"level":31},"b15","🎮 Try it yourself",{"id":135,"html":136,"type":86},"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":138,"html":139,"type":86},"b17","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":141,"html":142,"type":86},"b18","\u003C!-- playground:end -->",{"id":144,"html":145,"text":146,"type":101,"level":31},"b19","The types you&#39;ll actually reach for","The types you'll actually reach for",{"id":148,"html":149,"type":86},"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":151,"code":152,"type":90,"language":63,"highlight":153},"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":155,"html":156,"type":86},"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":158,"html":159,"text":159,"type":101,"level":31},"b23","The gradient animation you gave up on",{"id":161,"html":162,"type":86},"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":164,"code":165,"type":90,"language":63,"highlight":166},"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":168,"html":169,"type":86},"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":171,"html":172,"type":86},"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":174,"html":175,"text":176,"type":101,"level":31},"b28","The hidden benefit: \u003Ccode>initial-value\u003C\u002Fcode>","The hidden benefit: initial-value",{"id":178,"html":179,"type":86},"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":181,"html":182,"type":86},"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":184,"html":185,"type":86},"b31","\u003Cp>Register the property with a valid \u003Ccode>initial-value\u003C\u002Fcode> and the browser has a real fallback:\u003C\u002Fp>",{"id":187,"code":188,"type":90,"language":63,"highlight":189},"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":191,"html":192,"type":86},"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":194,"html":195,"text":195,"type":101,"level":31},"b34","Browser support, honestly",{"id":197,"html":198,"type":86},"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":200,"html":201,"type":86},"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":203,"html":204,"type":86},"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":206,"html":207,"type":86},"b38","\u003C!-- quiz:start -->",{"id":209,"html":210,"text":210,"type":101,"level":31},"b39","🧠 Test yourself",{"id":212,"html":213,"type":86},"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":215,"html":216,"type":86},"b41","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":218,"html":219,"type":86},"b42","\u003C!-- quiz:end -->",{"id":221,"html":222,"text":222,"type":101,"level":31},"b43","What just became possible",{"id":224,"html":225,"type":86},"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":227,"html":228,"type":86},"b45","\u003Cp>The custom property was always there. The type is what was missing.\u003C\u002Fp>",{"id":230,"html":231,"type":86},"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":233,"type":234},"b47","divider",{"id":236,"html":237,"type":86},"b48","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":239,"type":240,"items":241,"ordered":18},"b49","list",[242,243,244,245,246],"⭐ \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":50,"canonical":249,"description":250},"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":253,"locked":18},"019fe65f-fdb5-7369-a3ad-9918a64f3799",[255],{"id":33,"slug":34,"title":36,"_count":256},{"questions":39},[258],{"locale":13,"slug":34},{"id":33,"slug":34,"title":36,"_count":260,"questionCount":39},{"questions":39}]