CSS @property: advanced theming
Source: Dev.to
Defining Local Variables with @property
@property --box-radius {
syntax: "*";
inherits: false;
initial-value: 16px;
}
@property --box-background {
syntax: "*";
inherits: false;
initial-value: #a2a3a3;
}
.box {
background: var(--box-background);
border-radius: var(--box-radius);
}
You can then apply variations by overriding the local properties:
.box-dark { --box-background: #626262; }
.box-light { --box-background: #d4d6d7; }
.box-rad-s { --box-radius: 8px; }
.box-rad-l { --box-radius: 32px; }
Because the properties are non‑inheriting, nesting boxes does not cause the parent to affect the child:
<div class="box">
Parent box with customized property values
<div class="box">
Child box with initial property values
</div>
</div>
Linking Local Variables to Global Values
To avoid hard‑coding values, define global variables in :root and reference them as fallbacks:
:root {
--global-background: light-dark(#dedede, #676767);
--global-radius: 16px;
}
/* Local properties (no initial values needed) */
@property --box-radius {
syntax: "*";
inherits: false;
}
@property --box-background {
syntax: "*";
inherits: false;
}
/* Component styles */
.box {
background: var(--box-background, var(--global-background));
border-radius: var(--box-radius, var(--global-radius));
}
/* Theme variations */
.box-dark { --box-background: oklch(from var(--global-background) calc(l - 0.2) c h / alpha); }
.box-light { --box-background: oklch(from var(--global-background) calc(l + 0.2) c h / alpha); }
.box-rad-s { --box-radius: calc(var(--global-radius) / 2); }
.box-rad-l { --box-radius: calc(var(--global-radius) * 2); }
Now the entire theme can be driven by a handful of global variables, while individual components still retain their own local overrides.
Introducing Computed Variables
When many components need similar transformations, it’s useful to introduce an intermediate layer of computed variables. These act as a bridge between the global API and the local implementation, keeping the design predictable.
:root {
/* Global vars */
--global-background: light-dark(#dedede, #676767);
--global-radius: 16px;
/* Computed vars */
--radius-s: calc(0.5 * var(--global-radius));
--radius-m: var(--global-radius);
--radius-l: calc(2 * var(--global-radius));
--background-normal: var(--global-background);
--background-dark: oklch(from var(--global-background) calc(l - 0.2) c h / alpha);
--background-light: oklch(from var(--global-background) calc(l + 0.2) c h / alpha);
}
/* Local properties */
@property --box-radius {
syntax: "*";
inherits: false;
}
@property --box-background {
syntax: "*";
inherits: false;
}
/* Component styles */
.box {
--box-background: var(--background-normal);
--box-radius: var(--radius-m);
background: var(--box-background);
border-radius: var(--box-radius);
}
/* Theme variations */
.box-dark { --box-background: var(--background-dark); }
.box-light { --box-background: var(--background-light); }
.box-rad-s { --box-radius: var(--radius-s); }
.box-rad-l { --box-radius: var(--radius-l); }
With this structure, the theme exposes only a few high‑level variables (--global-background, --global-radius). The computed layer defines the permissible variations, and the local layer consumes those values without directly accessing the globals. This separation enforces consistency and makes the theme easier to extend.
Conclusion
Using three layers of CSS variables—global, computed, and local—provides a clean, predictable theming system that aligns with the Open‑Closed Principle. Future enhancements could expose the computed layer via a @function at‑rule, further protecting internal values from accidental changes.
Enjoy your frontend development!