Skip to main content

Bridging the Browser Gap: Polyfilling CSS random() for Cross-Browser Controlled Chaos

In his book How to Be Perfect, written as a companion to the philosophical comedy series The Good Place, showrunner Mike Schur dedicates a chapter titled "The Luck of the Draw" to exploring how the myth of meritocracy leads people to systematically underestimate the role of chance in their lives. The idea that randomness and unpredictability govern fundamental aspects of reality—much like the quantum mechanical premise that God plays dice with the universe—has increasingly found a home in modern digital design. As web creators move toward layouts that exist in a subtle state of flux, digital interfaces are reflecting the ancient Heraclitean maxim that one cannot step into the same river twice.

While extreme applications of non-deterministic user interfaces, such as generative UI features tested in search engine results, have drawn mixed reactions from users, presentational randomness offers compelling benefits when applied with restraint. Yet implementation has traditionally depended heavily on JavaScript or complex build-time tooling. With the emergence of the CSS random() specification, declarative style sheets are finally stepping up to handle controlled chance natively, even as full cross-browser availability remains out of reach.

Real-World Use Cases for Presentational Randomness

In greenfield web projects and consulting engagements, emerging user interface trends often manifest in dynamic micro-interactions. A frequent example is the custom confetti explosion designed to celebrate a user completing a randomized draw or reaching a milestone. While conceptually simple, brand-driven requirements often mandate granular control over particle behavior, colors, trajectories, and timing.

In many development pipelines, these requirements eventually outgrow off-the-shelf JavaScript libraries. Teams routinely end up abandoning third-party plugins to write custom scripts capable of fine-tuning every randomized particle to match brand guidelines. This recurring challenge highlights an ongoing tension in user experience design: balancing presentational chaos with explicit brand control.

For years, web standardization bodies have operated under the philosophy of "paving the cowpaths"—identifying common user interface patterns developed with JavaScript and formalizing them into declarative HTML and CSS standards. Providing a native mechanism for controlled randomness in the CSS presentation layer directly fulfills this mission.

By late 2025, Apple’s WebKit engine became the first to implement native support for the CSS random() specification in Safari 26.2. The release focused heavily on allowing developers to resolve widespread design requirements using standard HTML and CSS alone, drastically reducing reliance on JavaScript frameworks.

Following Safari’s release, developers across the web community showcased how native CSS randomness simplifies complex visual design. Web developer Schalk Neethling demonstrated fine-grained control over confetti effects using purely declarative CSS. Web writer Alvaro Montoro argued that CSS is inherently the most suitable layer for presentational randomness, invoking the W3C’s Rule of Least Power—the design principle that encourages solving web development problems using the least powerful language capable of expressing the solution.

Despite these advantages, cross-browser adoption remains fragmented. Months after Safari introduced native CSS random(), standard support across other major engines remains unconfirmed. While bug trackers for both Chromium and Firefox indicate active development, neither browser has shipped the feature publicly, even behind experimental flags. Furthermore, because Safari browser updates are tied directly to macOS and iOS operating system releases, many Apple users remain unable to access native random() capabilities on older system software.

Attempting to bridge this gap presents significant technical obstacles. The syntax outlined in the CSS Values and Units Module Level 5 editor’s draft is intricate, featuring elaborate caching semantics, keying structures, base values, and step intervals. Moreover, because the specification remains in early exploration, major breaking changes to the syntax are expected. Combined with the historic risks associated with CSS polyfills—which often require re-fetching, parsing, and rewriting entire style sheets at runtime—bringing random() to non-Safari browsers requires a specialized architecture.

Polyfilling CSS random() Across Modern Browsers

To make declarative CSS randomness accessible across all major browser engines without waiting for vendor baseline support, a light-weight client-side solution was packaged as css-random-polyfill. Rather than attempting to re-parse the full document object model or invent non-standard CSS selectors, the library leverages valid CSS custom property declarations to handle evaluation safely across Chromium, Firefox, and older Safari instances.

In Safari preview builds, the WebKit team demonstrated several key capabilities of native CSS randomness, ranging from animated visual starfields to complex grid distributions. Recreating these demonstrations across browsers provides a clear picture of how the polyfill operates and how developers can structure their styles to remain forward-compatible with native implementations.

Starfields and Step Intervals

The primary demonstration published by the WebKit team features a dynamic starfield: a scattering of small circular elements fading in and out at randomized speeds and delays, interspersed with larger four-pointed stars tilted at uniform angles.

To adapt this demonstration for cross-browser support via css-random-polyfill, the HTML document includes the library script and marks target elements with a specific designated class:

<script src="https://unpkg.com/css-random-polyfill@latest/dist/css-random-polyfill.js"></script>

<div class="randomized star"></div>
<div class="randomized star"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>

In the corresponding CSS, instead of declaring random() expressions directly inside standard property declarations, values are assigned to intermediate custom properties prefixed with --random. Because CSS variables accept arbitrary expression values as valid syntax, non-supporting browsers store the string without discarding the rule.

.star 
  --random-star-size: random(1px, 7px, 1px);
  background-color: white;
  border-radius: 50%;
  aspect-ratio: 1/1;
  width: var(--random-star-size);
  position: fixed;

  --random-top: random(0%, 100%);
  --random-left: random(0%, 100%);
  top: var(--random-top);
  left: var(--random-left);

  --random-hue: random(0, 360);
  filter: drop-shadow(0px 0px calc(var(--random-star-size) * 0.7) oklch(0.7 0.2 var(--random-hue)))
    drop-shadow(0px 0px calc(var(--random-star-size) * 3) white);
  mix-blend-mode: hard-light;

  --random-speed: random(2s, 5s);
  animation: fade-in var(--random-speed);
  animation-iteration-count: infinite;

  --random-delay: random(2s, 5s);
  animation-delay: var(--random-delay);
  animation-direction: normal;

This implementation demonstrates several core features of the random() specification. The third parameter in --random-star-size: random(1px, 7px, 1px) defines a step interval, restricting output values strictly to whole integer increments within the specified minimum and maximum bounds.

Additionally, to ensure all four-pointed star elements tilt at the exact same angle across the screen, the CSS utilizes the element-shared keyword:

.star.fourpointed 
  --random-rotation: random(element-shared, -45deg, 45deg);
  rotate: var(--random-rotation);

When native browser support is detected—such as in modern versions of Safari—the polyfill automatically steps aside, allowing the browser engine to execute the random() calls natively. When native support is absent, the script resolves the intermediate --random properties into static computed values.

Grid Positioning and Inline Call Evaluation

A second scenario tests multi-variable substitution and complex grid placement. In a 100×100 layout grid, individual cells are assigned randomized spatial dimensions and background colors.

The polyfill handles nested variable resolution and multiple random() calls within a single custom property string. For example, layout bounds can be generated dynamically using custom properties passed directly into the random() function, which are then combined into shorthand properties like grid-area:

.rectangle 
  --random-grid-area: random(1, var(--rows), 1) / random(1, var(--columns), 1);
  grid-area: var(--random-grid-area);

This ensures that calculated values—such as dynamic row and column boundaries—are substituted accurately prior to the evaluation of the randomized range.

Wheel of Fortune and Typed Arithmetic

A demonstration created by Safari engineer Tim Nguyen illustrates a spinning wheel of fortune animation. The wheel spins from a fixed origin and settles at a randomly determined rotational angle:

@keyframes spin 
  from 
    rotate: 0deg;
  
  to 
    rotate: var(--random-rotation);
  


#wheel 
  --random-rotation: random(2turn, 10turn, 20deg);

This example highlights the specification’s support for CSS typed arithmetic. The minimum and maximum bounds are specified in full turns (2turn and 10turn), while the step interval is defined in degrees (20deg). Under the CSS specification, mixing units is valid provided they resolve to the same underlying data type—in this case, angular dimension.

To maintain simplicity and performance, the polyfill evaluates computed styles once during initial page render. While native implementations will eventually support continuous re-evaluation inside dynamic keyframe sequences or interactive checkbox triggers, initial-load evaluation provides a robust fallback for the majority of presentation use cases.

Value Sharing via Custom Caching Keys

CodePen exercises by web developer Chris Coyier demonstrate essential layout randomizations, such as scattering colored squares across a canvas. Extending this pattern allows testing custom caching keys, which force separate properties to share the exact same generated random value.

--random-height: random(--side, 40px, 100px);
--random-width: random(--side, 40px, 100px);

width: var(--random-height);
height: var(--random-width);

By passing a custom identifier (in this case, --side) as the initial argument, both --random-height and --random-width generate identical pixel dimensions for an element, preserving a perfect 1:1 aspect ratio without requiring explicit manual synchronizations in JavaScript.

Simulating random-item() in Chromium Environments

While numeric ranges cover many design requirements, web authors frequently need to select randomly from a discrete list of explicit values, such as a curated palette of brand colors. The CSS Values and Units Module Level 5 defines a dedicated random-item() function for this purpose:

/* Proposed native syntax */
color: random-item(element-shared, red, blue, green);

Because random-item() is not yet broadly supported in stable browser builds, alternative techniques are necessary for picking from discrete collections. In Chromium environments that support experimental CSS custom functions (@function) and inline conditionals (if()), developers can combine these emerging features with numeric random() declarations to simulate discrete selection.

By defining a custom CSS function that maps numeric index values to discrete parameters, developers can construct a generalized lookup utility:

@function --item(--index,
  --arg-1: ,
  --arg-2: ,
  --arg-3: ,
  --arg-4: ,
  --arg-5: ,
  --arg-6: ,
  --arg-7: ,
  --arg-8: ,
  --arg-9: ,
  --arg-10: ) 

  result: if(
    style(--index: 1): var(--arg-1);
    style(--index: 2): var(--arg-2);
    style(--index: 3): var(--arg-3);
    style(--index: 4): var(--arg-4);
    style(--index: 5): var(--arg-5);
    style(--index: 6): var(--arg-6);
    style(--index: 7): var(--arg-7);
    style(--index: 8): var(--arg-8);
    style(--index: 9): var(--arg-9);
    else: var(--arg-10);
  );

This custom function accepts an --index parameter followed by optional item arguments (defaulted to empty values). It uses style container queries within an inline if() expression to return the argument matching the generated index.

A layout element can then combine numeric CSS randomness with the custom function to select discrete colors declaratively:

--random-index: random(element-shared, 1, 5, 1);
--random-color: --item(var(--random-index), aqua, purple, pink, grey, green);

Unlike legacy workarounds designed exclusively for color types, custom functions provide a typed, spec-compliant mechanism capable of selecting arbitrary CSS values, including lengths, images, or transform strings.

Polyfill Architecture and Engine Mechanics

Understanding how css-random-polyfill safely processes expressions without causing layout instability requires looking at its underlying architecture. Rather than building a mathematical parsing engine from scratch, the library incorporates @csstools/css-calc, an open-source evaluation package maintained as part of the CSSTools ecosystem. Originally designed for build-time PostCSS transformations, @csstools/css-calc was updated to support the latest W3C random() caching and evaluation specification.

When executed in a client browser, the polyfill follows a structured process to resolve styles safely:

  1. Feature Detection: The polyfill executes a feature check using CSS.supports("width", "random(0px, 100px)"). If the browser natively understands the random() syntax, execution halts immediately to prevent unnecessary processing overhead.
  2. Layout Flash Prevention: If native support is absent, a temporary dynamic <style> tag is injected into the document head, setting .randomized display: none; to prevent flashes of unstyled content (FOUC) while calculations occur.
  3. Identifier Initialization: Unique scope identifiers are generated using standard browser APIs, including crypto.randomUUID() for document-level scope tracking and a WeakMap instance to associate persistent unique identifiers with specific DOM elements.
  4. Computed Style Extraction: The script queries all elements marked with the .randomized class and iterates through their computed styles via getComputedStyle(). It specifically filters for properties matching the --random naming convention.
  5. Syntax Patching: Unscoped random() expressions are normalized to include standard deterministic seeds using Math.random(), ensuring un-cached calls produce distinct distributions across elements.
  6. Mathematical Resolution: The patched property strings are evaluated through the @csstools/css-calc calculation engine, passing the current element’s UUID, property name, and document scope to preserve spec-compliant caching rules.
  7. Property Injection and Cleanup: The calculated, canonical CSS values are applied directly to the target element’s inline styles, and the temporary hiding stylesheet is removed from the DOM.

By restricting evaluation to custom property declarations beginning with --random, the polyfill relies entirely on valid CSS variable syntax. Browsers comfortably parse and store arbitrary variable strings in the computed style tree, allowing client-side JavaScript to read, process, and resolve expressions cleanly without re-fetching external assets or parsing raw CSS text files.

As browser engines continue to iterate on the CSS Values and Units Module Level 5 specification, client-side polyfills offer web developers an immediate path to experiment with declarative randomness today, laying the groundwork for widespread adoption as native support expands across the web ecosystem.

Siti Muinah

Author at DesignEnt.

Flash News