CSS random() looks like a math function for visual jitter, but the hard part is not drawing a number—it is deciding which calls share the same draw. After Safari 26.2 shipped an early implementation, the CSSWG reworked the caching semantics (#13132, #13601): a bare --foo is now globally shared, and you narrow that sharing with element-scoped, property-scoped, and property-index-scoped. That model is what CSS Values 5 specifies, and Safari 26.5 followed it. As support spreads, writing without understanding the cache key tends to land you—unintentionally—on either “identical squares everywhere” or “fully independent rectangles.”
CSS is declarative: functions have no evaluation clock. A UA may recompute a value as often as it likes, in any order. Randomness is inherently stateful. The spec therefore introduces a random cache name. Any random() / random-item() with the same cache key yields the same random base value (a pseudo-random number in [0, 1)); different keys get independent base values. Authors do not say when to roll—they say along which axes to align or split the key.
The canonical lab is a set of .box elements:
.box {
width: random(???, 100px, 200px);
height: random(???, 100px, 200px);
border: thin solid;
}
Only the ??? changes the outcome: scattered rectangles, one shared square, per-element squares, or identical rectangles. Understanding value sharing means being able to explain that fork.
Each call obtains a base value R ∈ [0, 1). random(min, max) maps roughly to min + R * (max - min) (with optional stepping). Critically, identical keys share R even when min/max differ. So width: random(--pulse, 100px, 200px) and animation-duration: random(--pulse, 1s, 2s) with R = 0.25 become 125px and 1.25s. Correlation comes from the key; scaling comes from each call’s range.
A cache name is conceptually a tuple (§9.4):
<dashed-ident>, or nullMatching keys ⇒ same R; differing keys ⇒ distinct R. Other arguments (min/max/step) do not participate in sharing.
<random-key><random-key> = auto | <random-cache-key> | fixed <number [0,1)>
<random-cache-key> =
<dashed-ident> || element-scoped
|| [ property-scoped | property-index-scoped | <ident> ]
Omitted or auto means maximum randomness, rewritten as element-scoped property-index-scoped. Keys split per element, per property, and per occurrence index inside a multi-component value. On .box, width and height diverge, and so do elements → scattered random rectangles.
| Spec | What enters the key | .box outcome |
|---|---|---|
omitted / auto |
element + property+index | scattered rectangles (max random) |
--foo alone |
name only (others null) | identical squares (min random = global share) |
element-scoped or --foo element-scoped |
element (+ optional name) | different squares per element |
property-scoped or --foo property-scoped |
property name (+ optional name) | identical rectangles (width ≠ height) |
property-index-scoped |
property name + occurrence index | separate slots in one property, still alignable across elements |