Skip to main content

Bridging the Nondeterministic Web: Bringing Native CSS Randomness Across Browsers with a New Polyfill

In his book How to Be Perfect, Michael Schur, the creator of the philosophical comedy series The Good Place, devotes a chapter titled “The Luck of the Draw” to a critique of the myth of meritocracy. Schur argues that people frequently underestimate the vast role that plain chance plays in shaping their lives and accomplishments. That notion—that reality is governed by underlying stochastic forces, or that God, as Albert Einstein famously mused, plays dice with the universe—finds an intriguing parallel in modern digital interface design. As websites increasingly embrace controlled chaos and nondeterministic user experiences, art appears to mirror life in unexpected ways.

From dynamic layouts to unpredictable visual flourishes, web design has steadily gravitated toward subtle unpredictability. While extreme manifestations like generative UI remain a point of active debate—evidenced by mixed user reactions to automated design generation in search engines—there remains a distinct aesthetic appeal in creating web pages that exist in a state of continuous flux. Much like the ancient philosopher Heraclitus noted that one cannot step into the same river twice, a dynamic webpage can present a slightly varied facet of itself each time a user visits.

Real-world use cases for randomness

The demand for intentional randomness in user interfaces is not merely theoretical; it is actively shaping modern client projects. In short-term, greenfield consulting initiatives, emerging client requests often serve as an early indicator of broader design trends. In one recent project, the client sought to introduce a sense of celebration by firing a burst of randomized confetti particles whenever a user ran a custom-configured draw.

What initially seemed like a simple decorative request quickly escalated into a complex engineering task. To align every particle’s trajectory, color, and behavior with strict corporate branding guidelines, existing JavaScript confetti plugins had to be discarded in favor of a bespoke, ground-up implementation. The experience underscored an enduring tension in user experience design: the delicate balance between chaos and control. It also raised a fundamental structural question for web developers: why rely on complex JavaScript logic for purely visual randomness when presentational choices ideally belong in the CSS layer?

The CSS random() function emerges

Web standards historically evolve by codifying established visual patterns into declarative specifications—a philosophy often summarized by the W3C design principle to "pave the cowpaths." In line with this tradition, late 2025 marked a major milestone when Safari became the first major browser to ship support for the native CSS random() function as part of its WebKit engine update. The feature arrived with an explicit goal: allowing developers to resolve common dynamic UI requirements purely within HTML and CSS, minimizing reliance on heavy JavaScript frameworks.

The release sparked widespread interest across the front-end community. Web developer Schalk Neethling showcased how native CSS random() could deliver precise control over confetti particle physics entirely within stylesheet rules. Front-end engineer Alvaro Montoro highlighted that native CSS randomness aligns cleanly with the Rule of Least Power—a core architectural principle encouraging developers to choose the least powerful programming language capable of solving a given problem.

However, cross-browser adoption remains uneven. More than six months after Safari’s initial deployment, implementation timelines for competing engines remain uncertain. While public issue trackers for Chromium and Firefox show active development and discussion, neither platform has committed to a definitive release schedule or enabled the feature behind experimental browser flags.

This fragmentation leaves developers in an awkward position: testing advanced declarative CSS features on WebKit-based devices while being unable to run the same code natively on Windows or Linux workstations. Compounding the challenge, the underlying CSS Values and Units Module Level 5 specification remains an early-stage Editor’s Draft. Its intricate syntax—which encompasses complex caching mechanisms, keying semantics, base values, and step intervals—remains subject to potential breaking changes. Furthermore, polyfilling CSS functions has traditionally been notoriously difficult, often requiring complex stylesheet parsing or runtime DOM manipulation.

Let’s polyfill CSS random()

The browser availability gap created an unusual reversal of historic front-end norms. Developers reacted with surprise on public forums, noting how rare it was for cutting-edge CSS features to launch exclusively on Safari while remaining unavailable in Chromium-based browsers. Because Safari updates are tied directly to macOS and iOS operating system releases, even Apple users on legacy hardware are frequently left without support for newly introduced browser capabilities.

To resolve these cross-browser limitations today, a new lightweight utility package named css-random-polyfill has been published to npm. The polyfill bridges the gap between Apple’s WebKit implementation and non-supported environments, drawing directly from the original concepts and demonstrations released by the Safari team.

Demo: Random starfield

To demonstrate cross-browser parity, consider the classic starfield animation originally created by Apple’s WebKit team. The effect features 200 twinkling star elements randomly distributed across a viewport, fading in and out at varied timing intervals, casting multi-hued shadows, and rotating at random angles.

To make the WebKit-exclusive code compatible across Chromium and Firefox using the polyfill, the HTML references the polyfill script and marks candidate elements with a designated class name:

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

<!-- Star elements marked with the "randomized" class for target processing -->
<div class="randomized star"></div>
<div class="randomized star"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>

On the CSS side, because browsers parse custom property declarations containing unknown functional syntax without throwing syntax errors, values can be stored inside custom CSS variables using a standardized --random prefix:

.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 exercises several parameters of the proposed specification, including step intervals that lock randomized numeric outputs to discrete step increments:

--random-star-size: random(1px, 7px, 1px);

It also leverages the element-shared keyword to ensure that specific elements, such as four-pointed stars, share uniform rotation values across their individual geometry:

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

While native browser implementations allow random() to be called directly inside inline property declarations—much like calc() or min()—the polyfill’s custom property mapping convention ensures backward and forward compatibility. When opened in native Safari, the polyfill steps aside, allowing the browser engine to resolve the calls natively.

Demo: Random colored grid cells

Another illustrative use case originally highlighted by the WebKit team involves generating dynamic multi-colored CSS grid systems. While generating a 100×100 randomized color grid is primarily a stress test rather than a common design pattern, it offers a useful benchmark for evaluating complex nested functional arguments.

The polyfill handles nested custom property substitution and inline calculation merging. For example, developers can calculate dynamic grid-area boundaries by combining multiple random() queries into single shorthand declarations:

.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 implementation scenario, authored by Safari engineer Tim Nguyen, uses CSS randomness to drive visual UI components like dynamic prize wheels.

In this scenario, the rotation calculation demonstrates unit flexibility, mixing complete turns (turn) with degree increments (deg):

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


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

The CSS specification permits mixing physical units within random() parameters provided they share the same dimensional data type—in this case, angular measurement—relying on standard CSS typed arithmetic rules similar to those used by calc(). To ensure compatibility with the polyfill, static values are computed during initial page initialization, prioritizing execution simplicity over dynamic re-evaluation.

Demo: Random squares

A minimal example popularized by web developer Chris Coyier involves rendering randomly positioned geometric shapes with varying dimensions. Expanding on that concept, custom caching keys can be introduced to synchronize dimensions across separate properties.

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

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

By supplying a matching custom identifier (--side) as the first argument, the polyfill ensures that the random value generated for height is identically bound to width, preserving square aspect ratios without explicit CSS variable linking.

Chromium-only bonus demo: Simulate random-item using a custom CSS function

While random() handles numerical ranges cleanly, designers frequently need to pick randomly from a discrete set of non-numeric tokens, such as predefined brand color palettes. The CSS Values and Units Module Level 5 draft addresses this with a proposed random-item() function:

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

Because native browser engine support for random-item() remains extremely limited, developers operating in experimental Chromium builds can combine emerging CSS features—specifically @function custom functions and inline style() container queries—to emulate the behavior today:

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

The custom CSS function maps an integer index parameter against a series of optional argument slots using native conditional logic:

@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 workarounds that relied on type-specific hacks to simulate color arrays, constructing array-like selectors with declarative CSS custom functions provides a type-agnostic solution that works across arbitrary CSS values.

How the polyfill works

The underlying implementation of css-random-polyfill leans heavily on existing open-source tooling from the PostCSS ecosystem, specifically adapting the MIT-licensed @csstools/css-calc package. Originally designed for build-time stylesheet transformations, the calculation core was recently updated to support the latest working draft of the CSS random() specification.

The client-side polyfill bridges runtime CSS evaluation with this parsing engine through a concise execution pipeline:

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 ) --))([^,]+),/gi,
    (_, expression) => `random(fixed $Math.random(), $expression,`
  );

  return calcFn(patchedCss, 
    precision: 5,
    toCanonicalUnits: true,
    randomCaching: 
      documentID,
      elementID: elementIDs.getOrInsert(element, `element-$crypto.randomUUID()`),
      propertyName,
    ,
  );

The execution flow operates through several distinct phases:

  1. Feature Detection: The script queries CSS.supports() to verify whether the running browser engine natively understands the random() expression. If native support is present, execution halts immediately.
  2. Layout Shift Prevention: In non-supporting browsers, a temporary style rule visually hides target elements matching the .randomized selector, preventing unstyled content flash during initial calculation.
  3. Identifier Assignment: Unique session and element identifiers are assigned using crypto.randomUUID() and managed in memory via a WeakMap.
  4. Style Inspection: The script uses getComputedStyle() to read raw values assigned to declared --random custom properties across marked DOM elements.
  5. AST Resolution & Substitution: The raw variable values are passed into @csstools/css-calc, which parses the expression tree, applies deterministic seed caching rules, computes exact numerical bounds, and returns resolved values with canonical CSS units.
  6. Property Mutation & Unveil: Calculated outputs are written directly back to the element’s inline style declaration, and the temporary hiding styles are removed from the DOM.

By evaluating expressions via standard CSS custom properties (--random-*), the polyfill avoids the performance pitfalls historically associated with front-end CSS polyfills—such as re-fetching network stylesheets or executing expensive runtime CSS string parsers.

Random parting thoughts

The arrival of declarative randomness in CSS reflects a broader shift toward richer visual capabilities built directly into the open web platform. While developers wait for unified native adoption across Chromium, Gecko, and WebKit, polyfilling the specification provides an immediate path to experiment with dynamic layouts, particle effects, and generative design patterns without sacrificing cross-browser functionality.

Asep Darmawan

Author at DesignEnt.

Flash News