demo · v138

Unit converter playground

Before typed arithmetic, calc(200px / 1px) was a syntax error — type mismatch. With typed arithmetic it's a unitless number. This playground exercises four conversions that previously needed the tan(atan2(x, 1px)) hack — and drives a dial, a ruler, a colour, and a CSS counter from the result.

Behind a flag in earlier builds: typed arithmetic ships unflagged in Chrome 138 for length / time / angle / percentage. The dial and the counters below run entirely in CSS — no JavaScript reads or writes any computed style.
probing typed arithmetic…

↓ all four numbers below come from CSS calcs alone — JS only writes the two slider values.

length → number
calc(var(--px) / 1px)
em → percentage
calc(var(--em) / 1em * 100%)
%
length → angle
calc(var(--px) / 1px * 1deg)
0deg
px → percentage (÷4)
calc((var(--px) / 1px / 4) * 1%)

dial — rotation = calc(var(--px) / 1px * 1deg)

colour — red channel = calc(var(--px) / 1px), mapped via @property

interactive: drag the px slider
calc(var(--px) / 1px) → unitless number → drives needle rotation, swatch red channel, counters above

The four canonical conversions

/* OLD — pre-v138 you had to do this nonsense */
.legacy {
  --unitless: tan(atan2(var(--px), 1px));   /* the "atan2 trick" */
}

/* NEW — typed arithmetic */
.modern {
  --unitless:        calc(var(--px) / 1px);                /* <length> / <length> = <number> */
  --as-percentage:   calc(var(--em) / 1em * 100%);         /* multiply unitless back into a unit */
  --as-angle:        calc(var(--px) / 1px * 1deg);         /* cast from px space to deg space */
  --double-percent:  calc(20% / 0.5em * 1px);              /* percentage + em → length */
}

What's happening

  1. Each slider drives a CSS custom property (--px, --em). These are typed, registered with @property.
  2. The four conversion calcs each cast or compose units. The result feeds transform: rotate(…), rgb(), counter-reset, and width.
  3. CSS counters (which only accept integer) are the readout trick: counter-reset: emToPx calc((var(--em) / 1em) * 100) gives us a live numeric display straight from CSS arithmetic — no JavaScript needed to render the converted value.
  4. The dial's needle rotation comes from --px / 1px * 1deg — a length cast into an angle. Before v138 this was a hard syntax error.

see also