Kodokon kodokon.com

CSS variables and the calc/clamp functions

Master custom properties, how they resolve at runtime within the cascade, and the calc() and clamp() functions for fluid interfaces.

10 min · 3 questions

Open this lesson in Kodokon

Custom 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.

CSS
:root {
  --brand: #6c5ce7;
  --spacing: 8px;
}

.card {
  color: var(--brand);
  padding: calc(var(--spacing) * 2);
  border-color: var(--accent, currentColor);
}
Declaring, reading, and fallback values

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.

CSS
@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%;
}
A typed, animatable variable with @property

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.

CSS
:root {
  --fluid-title: clamp(
    1.5rem,
    1rem + 2.5vw,
    3rem
  );
}

h1 {
  font-size: var(--fluid-title);
  max-width: clamp(20ch, 60%, 65ch);
}
Fluid typography without a media query

Knowledge check

Make sure you remember the key points of this lesson.

  1. What happens if color: var(--gap) is evaluated while --gap is 12px?
    • The declaration is ignored and the previous color rule applies
    • The declaration becomes invalid at computed-value time and color resolves as unset
    • The browser converts 12px into a color close to black
    • An error is thrown in the console and the stylesheet is rejected
  2. What is clamp(1rem, 2.5vw, 2rem) strictly equivalent to?
    • max(1rem, min(2.5vw, 2rem))
    • min(1rem, max(2.5vw, 2rem))
    • calc(1rem + 2.5vw + 2rem)
  3. What is the main benefit of @property over a regular custom property?
    • It makes the variable accessible from JavaScript
    • It allows the variable to be used inside a calc()
    • It gives the variable a type, which notably makes it interpolatable in animations
    • It forces the variable to inherit throughout the whole document