Skip to main content

Bringing Controlled Chaos to the Web: Polyfilling CSS random() for Cross-Browser Declarative Design

In his book How to Be Perfect, Michael Schur, the creator of the acclaimed television series The Good Place, dedicates a chapter titled "The Luck of the Draw" to exploring how the myth of meritocracy leads individuals to underestimate the sheer role luck plays in their lives. The universe, as physicists and philosophers frequently observe, often operates on a roll of the dice. That reality has increasingly begun to reflect itself in modern web design, where developers and design systems are embracing controlled chaos to create subtle, dynamic front-end experiences.

From Heraclitus’s ancient assertion that one cannot step into the same river twice to modern experiments in generative user interfaces (GenUI)—such as Google’s explorations in search—the web is shifting away from static, rigid layouts toward pages that exist in a state of subtle flux. While aggressive forms of non-determinism in design remain a topic of debate among user experience professionals, presentational randomness has carved out a clear, practical niche in contemporary web development.

To make these unpredictable visual states easier to build without heavy scripting, browser standards bodies and layout engines have turned their attention toward natively supporting presentational randomness directly inside CSS. With Apple’s Safari taking the initial leap, developers now have a glimpse of a future where controlled visual variation requires no external JavaScript frameworks. However, with broader cross-browser adoption still pending, new polyfilling solutions are surfacing to bring standard CSS random() capabilities to all major browsers today.

Real-World Use Cases for Randomness

In client-side software engineering, the demand for randomized presentation arises frequently in greenfield projects and modern UI work. Enterprise user interfaces often incorporate controlled randomness to increase user engagement—such as triggering a burst of randomized confetti particles when a user executes a successful random draw or completes an action.

While a confetti effect appears straightforward on the surface, real-world corporate requirements frequently demand fine-grained control. Standard off-the-shelf JavaScript libraries often lack the customization needed to align particle movements, colors, and timing with strict corporate brand guidelines. In many cases, engineering teams find themselves abandoning external plugins to write bespoke implementations, highlighting an ongoing tension in front-end design between the need for visual chaos and the necessity of brand control.

This practical challenge raises a persistent question for front-end architecture: whether presentational randomness belongs in the JavaScript application logic, or if it can be handed off directly to the CSS presentation layer.

Historically, standardizing common UI patterns into declarative CSS has been a guiding principle for web spec authors. Following the principle known as "paving the cowpaths," CSS standards continually absorb techniques that developers previously implemented with complex scripts. In late 2025, Safari became the first browser to ship native support for the CSS random() function as part of the Safari 26.2 update. The implementation aligned with the W3C Rule of Least Power, which advocates for solving technical problems using the least powerful language capable of expressing the solution—in this case, preferring declarative CSS over imperative JavaScript.

Industry commentators and front-end developers quickly noted the significance of native CSS randomness. Developer Schalk Neethling demonstrated how CSS random() provides precise control over particle dynamics in confetti animations, while developer Alvaro Montoro argued that CSS is uniquely suited for presentational randomness because layout engines can evaluate non-deterministic values natively during render cycles.

However, cross-browser availability remains fractured. Half a year after Safari introduced native CSS random() support, Chrome and Firefox have bug tracker entries indicating active development, but neither engine has established a firm timeline for standard release or flagged implementations. Because Safari updates are tied directly to macOS and iOS operating system releases, even Apple users on legacy operating systems are unable to run native CSS random() executions without cross-browser polyfills.

Let’s Polyfill CSS random()

To bridge the gap between Safari’s engine-level implementation and the broader web ecosystem, developers have begun exploring client-side polyfills for the draft CSS Values and Units Module Level 5 specification. Polyfilling modern CSS presents structural challenges compared to JavaScript polyfills, as browsers historically discard CSS rules or functions they do not natively recognize during stylesheet parsing.

However, a polyfill package titled css-random-polyfill addresses this limitation by taking advantage of valid CSS custom properties (CSS variables). By storing random() expressions within custom properties that begin with a reserved prefix—such as --random—the browser’s CSS engine preserves the raw string value in the computed style tree. A lightweight JavaScript runtime can then read those values, compute the deterministic or randomized outputs based on the draft specification’s rules, and reapply the resolved values inline.

This architectural compromise keeps stylesheets forward-compatible. Once native support for random() achieves baseline availability across all browser engines, developers can remove the polyfill script reference without needing to rewrite their underlying CSS rules.

Demo: Random Starfield

To validate cross-browser behavior, early polyfill tests involved recreating reference demos originally published by Apple’s WebKit engine team. One primary example is a animated starfield display featuring two hundred randomly scattered stars that fade in and out at unpredictable intervals, with rotated four-pointed variants and subtle hue-shifted shadows.

To adapt the original Safari-only code for cross-browser execution using the polyfill, developers include a lightweight script reference and assign a marker class to the target DOM elements:

<!-- The polyfill script processes CSS random calls on load -->
<script src="https://unpkg.com/css-random-polyfill@latest/dist/css-random-polyfill.js"></script>

<!-- Star elements tagged with the "randomized" marker class -->
<div class="randomized star"></div>
<div class="randomized star"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>

In the CSS layer, variables storing random() functions are defined using the required --random prefix convention:

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

The starfield implementation exercises key features of the specification, including step intervals. Passing a third parameter into random(1px, 7px, 1px) restricts the output to whole integer steps within the specified boundary. Furthermore, elements sharing a rotational angle utilize the element-shared keyword to coordinate values across matching elements:

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

Demo: Random Colored Grid Cells

Another notable showcase of native CSS randomness highlighted by industry commentators, including CSS-Tricks founder Chris Coyier, involves a dense 100×100 CSS grid where individual grid cells receive randomized positions and multi-colored backgrounds.

The demo highlights the flexibility of the proposed syntax. Variable references passed inside a random() calculation resolve correctly, and multiple random() calls can be nested or combined inside standard CSS shorthand properties. For instance, a randomized grid-area value can calculate both start rows and start columns simultaneously:

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

Demo: Wheel of Fortune

A third practical test case, originally created by WebKit team engineer Tim Nguyen, demonstrates an animated spinning wheel whose final resting point relies on randomized angular rotation.

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


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

This example demonstrates unit mixing within CSS typed arithmetic. The function accepts combinations of turn and deg units because both express identical data types (angles). Under the CSS spec, different units within the same category resolve correctly during expression evaluation.

Currently, the css-random-polyfill implementation evaluates initial computed styles upon document loading. While dynamic re-evaluation upon continuous style changes or DOM mutations would require integration with MutationObserver or style monitoring APIs, the static startup computation satisfies the requirements for initial page renders.

Demo: Random Squares

To evaluate value-sharing features through custom caching keys, a minimal setup featuring randomly placed squares demonstrates how identical values can be reused across properties without duplicating code logic.

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

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

By specifying the custom --side caching key, both the height and width functions draw from the same evaluated random seed, guaranteeing that each square remains perfectly proportional while varying in size relative to other squares on the screen.

Chromium-Only Bonus Demo: Simulate random-item Using a Custom CSS Function

While numerical boundaries cover positional and dimensional needs, web developers frequently require random selections from discrete, non-numeric lists—such as choosing randomly from a predefined palette of brand colors. To address this, the CSS Working Group draft spec proposes a dedicated random-item() function:

random-item(element-shared, red, blue, green);

While full random-item() support is not yet widely shipped in stable browser releases, Chromium’s recent engine additions—specifically CSS Custom Functions (@function) and inline style conditionals (if())—make it possible to emulate list-based selection logic directly in CSS today.

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

By defining a generic CSS custom function, developers can map calculated indices to arbitrary parameter positions:

@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);
  );

Unlike historical workaround techniques restricted strictly to color types, combining modern CSS custom functions with inline logic allows developers to index through lists of arbitrary data types—including lengths, angles, keywords, or strings—using emerging web standards as intended.

How the Polyfill Works

Underlying the client-side polyfill is an integration with established open-source tooling from the CSSTools ecosystem. Rather than reinventing CSS expression parsing, css-random-polyfill imports and executes @csstools/css-calc, an open-source evaluation package originally built for PostCSS build steps that has been updated to support the latest CSS Values and Units Module Level 5 draft spec.

The client-side execution sequence follows a distinct pathway:

import  calc  from "@csstools/css-calc";
const calcFn = calc;

if (!CSS.supports("width", "random(0px, 100px)")) 
  const styleTag = document.createElement("style");
  styleTag.textContent = ".randomized  display: none; ";
  document.head.appendChild(styleTag);
  const elementIDs = new WeakMap();
  const documentID = crypto.randomUUID();

  document.querySelectorAll(".randomized").forEach((element) => 
    const styles = getComputedStyle(element);
    [...styles]
      .filter((property) => property.startsWith("--random"))
      .forEach((propertyName) => 
        const css = styles.getPropertyValue(propertyName);
        const value = resolveRandom(css, 
          element,
          propertyName,
          documentID,
          elementIDs,
          calcFn,
          crypto,
        );
      element.style.setProperty(propertyName, value);
    );
  );
  if (styleTag.parentNode) 
    styleTag.parentNode.removeChild(styleTag);
  


function resolveRandom(css,  element, propertyName, documentID, elementIDs, calcFn, crypto ) fixedb

When initialized, the polyfill first performs feature detection via CSS.supports(). If native support for random() is absent, it temporarily injects a dynamic style rule to hide targeted .randomized elements, preventing visual layout flashing during computation.

The script then generates document-level and element-level identifiers using crypto.randomUUID(), querying all elements marked with the .randomized class. It scans their computed styles for custom properties matching the --random prefix.

Each discovered CSS expression string is passed to resolveRandom(), which normalizes caching scopes and invokes calcFn to calculate exact canonical values (such as converting pixel or angular dimensions). Once calculated, the script updates the element’s inline styles with the processed result and removes the temporary hiding stylesheet to display the fully rendered layout.

By using valid CSS custom property structures to transport random() expressions, this technique bypasses traditional polyfill pitfalls—such as re-fetching entire network stylesheets or re-parsing complex CSS trees—offering a performant stopgap as cross-browser adoption progresses.

Ali Ikhwan

Author at DesignEnt.

Flash News