A dial laid out by ray()
CaveatA ray needs a point to start from, and engines have disagreed about which point. The current spec starts it at offset-position, which defaults to the centre of the containing block; earlier implementations started it wherever the element itself sat, so the same rule threw every tick off the dial. Setting offset-position: 50% 50% and also left: 50%; top: 50% makes both readings land on the same spot. closest-side measures to the containing block, so the dial needs a size of its own or every ray has length zero.
FallbackAn engine without offset-path would pile every tick in the middle, so the dial hides behind @supports not (offset-path: ray(0deg)) and the range input it is built on comes back as a plain slider. Keyboard and screen readers use that input in every case: arrow keys turn the dial, and dragging only ever writes to it. Where @property is missing the value still works, it just jumps instead of easing and the readout skips the numbers in between.
<div class="dial">
<i class="tick"></i> <!-- 41 of them -->
<input type="range" min="0" max="100">
</div>
@property --v {
syntax: "<number>";
inherits: true;
initial-value: 0;
}
.tick {
position: absolute; left: 50%; top: 50%;
offset-position: 50% 50%;
--a: calc(-135deg + var(--i) * 6.75deg);
offset-path: ray(var(--a) closest-side);
offset-distance: 94%;
offset-rotate: auto;
/* dim until the value reaches this tick */
opacity: clamp(.14, (var(--v) * .4 - var(--i)
+ 1) * 4, 1);
}