Skip to main content

Embracing Non-Determinism in Web Design: The Rise, Reality, and Polyfilling of Native CSS random()

In his tie-in book on moral philosophy, How to Be Perfect, Michael Schur—creator of the acclaimed television series The Good Place—devotes a chapter titled “The Luck of the Draw” to exploring how the myth of meritocracy leads individuals to systematically underestimate the role of chance in their lives. The notion that unpredictable forces govern outcomes is hardly new; from ancient philosophy to quantum physics, the idea that the universe plays dice has remained a persistent reality. On the modern web, art is increasingly imitating life as designers and developers begin embracing controlled chaos directly within site architectures.

While extreme forms of non-determinism—such as Google’s upcoming integration of generative UI in search engine results—have sparked debate among user experience professionals and commenters alike, there remains a compelling aesthetic in subtle web design dynamicism. Much like Heraclitus’s famous philosophical axiom that one cannot step into the same river twice, a growing subset of web experiences now aims to exist in a state of subtle flux each time a user loads a page.

Historically, introducing presentational randomness into a website required custom JavaScript, heavy frameworks, or complex build-time scripts. However, recent developments in standardizing declarative CSS have brought native randomness closer to browser engines. The arrival of the CSS random() specification marks a key shift in how frontend engineers handle unpredictability—and highlights the ongoing browser support gaps that have prompted developers to engineer client-side polyfills.

Real-world use cases for randomness

In modern web development, particularly within short-term and greenfield corporate projects, visual randomness frequently surfaces as a business requirement. A standard example is the celebratory display of digital confetti following a completed randomized draw or administrative action. While conceptually simple, bringing such features to life in production often uncovers a deeper engineering challenge: balancing user delight with client brand guidelines.

When building dynamic UI elements like customized confetti, developers often find off-the-shelf JavaScript plugins insufficient for meeting precise client specifications regarding brand colors, particle physics, and layout bounds. Custom implementations frequently become necessary, illustrating the ongoing technical tension between chaos and control in user experience design.

This tension has prompted frontend architects to question why presentational randomness cannot be natively managed directly within the CSS presentation layer. Historically, the Working Groups behind web specifications have sought to standardize common design patterns into native, declarative CSS—a process often described as "paving the cowpaths" to reduce dependency on third-party JavaScript libraries and complex frameworks.

In late 2025, Apple’s WebKit engine made a major move by adding initial support for the CSS random() specification in Safari 26.2. This release positioned Safari as the first major browser to offer native CSS randomness, prioritizing pure HTML and CSS solutions for presentation logic.

The community reaction to native CSS randomness was immediate. Developers and web performance advocates highlighted numerous practical implementations. Developer Schalk Neethling demonstrated how native CSS random() offers fine-grained control over customized confetti visual effects. Concurrently, CSS engineer Alvaro Montoro pointed out that declarative stylesheets are inherently the most appropriate layer for handling presentational randomness. Citing the World Wide Web Consortium’s (W3C) Rule of Least Power—which states that developers should solve technical problems using the least powerful language capable of expressing the solution—Montoro noted that moving layout variability into CSS eliminates unnecessary JavaScript engine execution.

Despite Safari’s early adoption, cross-browser compatibility remains a challenge. Months after Safari’s rollout, chromium-based browsers and Firefox have yet to deliver full production support. While issue trackers for both Google Chrome and Mozilla Firefox show active engineering discussions and active bug tracking, neither vendor has established a definitive public release timeline, even behind experimental browser flags. Consequently, web developers building cross-browser applications face a dilemma: build exclusively for Safari users or rely on client-side polyfills to bridge the gap.

Let’s polyfill CSS random()

The technical disparity created by Safari’s early adoption of CSS random() presents an unusual dynamic in modern frontend development. Engineers are generally accustomed to seeing emergent web features ship first in Chromium-based browsers, leaving Safari and iOS devices behind. With CSS random(), the situation is reversed, forcing non-Safari browser users to rely on fallback solutions to view native CSS randomness demonstrations.

Furthermore, even among Apple users, browser updates remain strictly tied to operating system release cycles. Users on older versions of iOS or macOS cannot run native random() properties without updating their entire operating system. To resolve these accessibility and compatibility constraints, developers have turned to building dedicated polyfills, such as the open-source css-random-polyfill package published on npm.

Polyfilling random() presents distinct technical challenges. Unlike simple property polyfills, the underlying specification draft for CSS random()—managed under the CSS Values and Units Module Level 5 draft—contains complex requirements. These include intricate random caching mechanisms, keying semantics, variable base values, and step intervals. Moreover, because the feature resides in an early-exploration editor’s draft, major breaking changes to the syntax remain expected.

Despite these obstacles, leveraging client-side scripts to interpret random() expressions allows developers to test declarative web design concepts today across Google Chrome, Mozilla Firefox, and older WebKit builds.

Demo: Random starfield

One of the primary showcase examples originally introduced by Apple’s WebKit preview team involves a dynamic, randomized starfield animation. In this demonstration, a field of stars fades in and out at randomized timing intervals across the screen. Larger four-pointed stars tilt at uniform, randomly generated angles, while each element features subtle ambient shadow effects generated with randomized hue parameters.

To make a demo originally written for native Safari compatible with other modern browsers, developers must include a client-side polyfill script and apply a designation class—such as .randomized—to target elements in the HTML document:

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

<!-- 200 star divs, we add the "randomized" marker class so css-random-polyfill knows which elements to target -->
<div class="randomized star"></div>
<div class="randomized star"></div>
<!-- additional star elements -->
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>

In terms of CSS structure, polyfilling a CSS function differs fundamentally from polyfilling a CSS selector. Because standard CSS parsers discard invalid properties, storing randomized values inside custom properties (CSS variables) prefixed with --random preserves the expressions without triggering parser drops in unsupported browsers:

.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 starfield layout demonstrates multiple syntax features defined in the standard specification draft. For example, passing an optional third argument within the function sets a step interval parameter, forcing calculated selections to round to specific numeric increments:

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

Similarly, applying the element-shared base value ensures that a single randomly generated angle applies uniformly across every element sharing that class key:

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

In native implementations, CSS random() functions can be written inline across standard property values, functioning similarly to calc() or min(). While the polyfill pattern relies on intermediate custom variables to process non-native code, it maintains functional backward compatibility. When executed in Safari builds with native support, the polyfill bypasses processing entirely, leaving performance execution to the underlying engine.

Demo: Random Colored Grid Cells

When analyzing the original starfield concepts showcased by WebKit engineers, web author Chris Coyier noted the visual appeal of native dynamic variables. By contrast, applying presentational randomness to structured grid layouts offers a clearer look at complex parameter scoping.

In a large 100×100 grid layout, randomized values can control cell positioning, color properties, and dimensions simultaneously. The polyfill handles variable substitution within inline functions, allowing multiple random() queries to combine into compound property definitions, such as the grid-area shorthand property:

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

Through this approach, developers can construct non-deterministic, generative CSS grid layouts directly within basic style definitions without running dedicated DOM layout routines in JavaScript.

Demo: Wheel of fortune

Another core implementation showcased during early technical previews was designed by Tim Nguyen of Apple’s WebKit development team. Nguyen demonstrated how random() parameters handle rotation transformations and dynamic animation calculations, such as simulating a spinning wheel of fortune.

In this layout, the terminal rotation variable mixes distinct measurement units across its range limits and step parameters:

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


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

The specification explicitly permits mixing compatible CSS measurement units (such as combining turns (turn) and degrees (deg)) as long as they resolve to the same underlying dimensional type. This relies on native CSS typed arithmetic mechanisms similar to those used within standard calc() evaluation.

To preserve stability within client-side polyfills, dynamic properties are calculated based on the computed styles established during initial DOM page loading. While runtime observers like MutationObserver could theoretically monitor computed property updates dynamically, executing client calculations during initial layout paint represents the most stable performance trade-off for current polyfill implementations.

Demo: Random squares

In a minimal test case originally compiled by Chris Coyier, dynamic layout properties are evaluated across basic block elements. Expanding on this approach allows testing how the spec handles custom identifier keys for synchronized parameter evaluation:

--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 key (such as --side), the evaluation engine guarantees that both the height and width properties map to the same calculated random seed, ensuring that target elements retain perfect 1:1 aspect ratios regardless of the final random bounds selected.

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

While numeric randomness covers properties like positioning, dimensions, and rotation angles, web designers frequently require dynamic selection from fixed, non-numeric lists of values, such as specific brand color palettes.

To address this need, the CSS Values and Units Module Level 5 draft outlines a proposed random-item() function:

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

The proposed random-item() syntax accepts a required caching parameter followed by an arbitrary sequence of discrete values. Aside from experimental technical preview builds in Safari, no production browser currently supports random-item() natively.

However, recent updates in Chromium-based browser builds—specifically experimental support for CSS custom functions (@function) and inline conditional logic (if())—allow developers to create functional client-side approximations of list-based selection features.

By combining numeric CSS random() outputs with custom functional evaluation, developers can construct a generalized item-selection function in CSS:

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

Underneath this interface, the custom --item function accepts a index parameter alongside an array of optional arguments, mapping numerical indices directly to specific list items using native CSS style conditionals:

@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 earlier workarounds devised by frontend engineers like Temani Afif—which relied on mathematical color hacks restricted exclusively to specific data types—the custom functional wrapper pattern accepts any valid CSS property type, demonstrating the flexibility of emerging web standard primitives.

How the polyfill works

Building a lightweight client-side polyfill for native CSS specifications requires leveraging existing parser infrastructure rather than rebuilding engine-level CSS parsers from scratch. The client-side implementation behind css-random-polyfill builds upon open-source tooling provided by the CSSTools project, specifically utilizing the standalone @csstools/css-calc package originally built for PostCSS build steps.

The engine handles client-side polyfill execution through a concise script architecture:

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 ) scoped)b

When evaluated in a browser environment, this polyfill follows a structured execution path:

First, the script executes a feature test using CSS.supports("width", "random(0px, 100px)") to detect native engine support. If native support is present (such as in modern Safari builds), the polyfill exits immediately, allowing the browser’s rendering engine to handle calculations natively.

If support is missing, the script temporarily injects a dynamic style element setting display: none on all .randomized elements. This prevents layout shift and visual flash during the initial style pass.

Next, unique tracking identifiers are assigned to the current document context and DOM elements using crypto.randomUUID() and a JavaScript WeakMap.

The engine then queries all target elements containing the .randomized marker class and scans their calculated styles using getComputedStyle(). It isolates custom properties matching the --random prefix, ensuring standard properties remain untouched.

For each targeted custom variable, the raw string value is passed to the resolveRandom helper function. This function patches expression caching scopes where explicit parameters are missing, assigning pseudo-random seeds (fixed Math.random()) to normalize non-keyed random instances.

The patched expression is then evaluated using @csstools/css-calc, configured with canonical unit resolution and five-decimal precision. This step resolves CSS units, performs arithmetic step interval rounding, and processes spec-compliant key caching across identical elements.

Finally, the computed numeric values are written directly back to the element’s inline style object as calculated CSS values, and the temporary hidden style rule is removed from the document head.

This architecture avoids traditional polyfill pitfalls, such as manually fetching external stylesheets over the network or re-parsing complex CSS codebases. By storing expressions inside custom properties, the browser’s internal parser preserves arbitrary functional strings as valid data types. This allows JavaScript runtime scripts to extract, compute, and apply polyfilled values efficiently.

Random parting thoughts

The implementation of css-random-polyfill highlights how open-source collaboration allows developers to adopt future CSS features well ahead of full browser engine alignment. By building upon existing spec-compliant calculation tools, frontend engineers can integrate declarative, presentational randomness into production websites today while ensuring a clean path toward native performance as Chrome and Firefox roll out native implementations.

Lina Irawan

Author at DesignEnt.

Flash News