Expressions

Every number in a file can be computed. Parentheses hold compile-time math, the stylesheet binds names, and the result bakes into the SVG as a literal.

Math where a number goes

{
  gap: 24;
  |box| { fill: --teal-wash; stroke: --teal-deep; }
}

|box#a| "8 * 2" { padding: (8 * 2); }
|box#b| "sqrt(2) * 60" { width: (sqrt(2) * 60); height: 60; }
|box#c| "clamp" { width: clamp(30, 200, 120); height: 60; }
8 * 2 sqrt(2) * 60 clamp

A ( … ) group is folded away when the file compiles. Inside it you get:

  • Arithmetic+ - * / ^, and comparisons.
  • A ternarycond ? a : b.
  • The math librarysqrt, abs, sin, cos, min, max, clamp, floor, round, pow, ln, exp.

A bare call like clamp(30, 200, 120) needs no group, because a call’s own parentheses already count as one.

The parens are where operators live for a reason: outside them - is a link and < is a marker, so the group is what tells the two apart.

Bindings

{
  direction: column; gap: 16; align: start;
  unit = 60;
  step(n) = (unit * 1.4 ^ n);
  |bar::box| { height: 34; fill: --amber-wash; stroke: --amber-deep; }
}

|bar| "step(0)" { width: step(0); }
|bar| "step(1)" { width: step(1); }
|bar| "step(2)" { width: step(2); }
|bar| "step(3)" { width: step(3); }
step(0) step(1) step(2) step(3)

name = value; in the stylesheet binds a scalar; name(params) = ( … ) binds a function. Both read anywhere a value goes, bare or inside a group.

The two signs do different jobs, and the difference matters:

  • = binds at compile time. A binding is a number the whole file shares, and it is gone by the time the SVG exists.
  • : sets a live property. That is what becomes a CSS variable.

So the four bars above are one geometric series, and nothing downstream can tell they were ever computed.

Parametric geometry

{
  direction: column; gap: 20;
  wave(a, f) = (u * 320, a * sin(2 * pi * f * u));
}

|line#sine| { points: (u * 320, 24 * sin(2 * pi * 3 * u)); samples: 64; stroke: --sky-deep; }
|line#named| { points: wave(18, 2); samples: 64; stroke: --rose-deep; }

points: on a |line| or |poly| may be an expression in u. That variable sweeps 0 → 1, sampled samples: times into a vertex list — which is how you get a wave, a spiral or a curve with no vertex typed.

A bound function returning a point keeps the formula named, as wave(a, f) does for the rose line.

Charts bind x the same way for a series’ fn:Charts has it.

Locals

{ gap: 30; }

|line#hex| {
  points: (r = 40; n = 6; a = 2 * pi * round(u * n) / n; (r * cos(a), r * sin(a)));
  samples: 7; fill: --purple-wash; stroke: --purple-deep;
}
|line#oct| {
  points: (r = 40; n = 8; a = 2 * pi * round(u * n) / n; (r * cos(a), r * sin(a)));
  samples: 9; fill: --sky-wash; stroke: --sky-deep;
}

Inside a group, name = expr; binds a local for the rest of that group, and the final expression is its value — no keyword and no return. A top-level comma makes that value a point.

So one expression draws a regular polygon: n corners at radius r, sampled once per corner and once more to close it.

Everything here bakes. An exported SVG never depends on host CSS for a size.

Go deeper

Colour, variables & expressions has the full grammar — constants, scientific notation, and what a value may be.