Why this now

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

Problem framing

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.

How it works

Base value and cache name

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):

  1. name — author <dashed-ident>, or null
  2. property / index — property name, or property name plus index within the declaration
  3. element — identifier for the element (or pseudo-element / custom-function “hypothetical element”)
  4. document — document identifier

Matching keys ⇒ same R; differing keys ⇒ distinct R. Other arguments (min/max/step) do not participate in sharing.

Reading <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