Master custom properties, how they resolve at runtime within the cascade, and the calc() and clamp() functions for fluid interfaces.
Open this lesson in KodokonCustom properties are not preprocessor variables. A Sass variable is replaced at compile time; a --brand property is resolved at runtime, participates in the cascade, inherits like any other property, and can be changed by a media query, a class, or JavaScript via setProperty(). That is what makes dynamic theming, dark mode without recompilation, and CSS/JS communication without a re-render possible.
:root {
--brand: #6c5ce7;
--spacing: 8px;
}
.card {
color: var(--brand);
padding: calc(var(--spacing) * 2);
border-color: var(--accent, currentColor);
}The second argument of var() is a fallback value, used only when the property is not defined. Classic gotcha: if --accent is 12px and you inject it into color, the fallback is not used. The declaration becomes invalid at computed-value time: the property falls back to its inherited or initial value, not to the previous declaration as a regular syntax error would.
The @property rule fixes two major limitations: it gives the variable a type (and therefore validation) and makes it animatable, since the engine now knows how to interpolate between two typed values instead of treating the variable as an opaque string.
@property --progress {
syntax: "<percentage>";
inherits: false;
initial-value: 0%;
}
.gauge {
background: conic-gradient(
var(--brand) var(--progress),
#e5e7eb 0
);
transition: --progress 0.4s ease-out;
}
.gauge.is-full {
--progress: 100%;
}On the math side, calc() lets you mix units (calc(100% - 2rem)), which is impossible with a preprocessor since the resolution depends on the rendering context. clamp(min, ideal, max) is sugar for max(min, min(ideal, max)) and beats stacks of breakpoints for fluid typography: the ideal value, often expressed in vw or cqi, glides freely between the two bounds.
:root {
--fluid-title: clamp(
1.5rem,
1rem + 2.5vw,
3rem
);
}
h1 {
font-size: var(--fluid-title);
max-width: clamp(20ch, 60%, 65ch);
}color: var(--gap) is evaluated while --gap is 12px?color rule appliescolor resolves as unset12px into a color close to blackclamp(1rem, 2.5vw, 2rem) strictly equivalent to?@property over a regular custom property?calc()