In his book on moral philosophy, How to Be Perfect, the creator of the television series The Good Place devotes a chapter titled "The Luck of the Draw" to exploring how the myth of meritocracy prompts individuals to systematically underestimate the role of chance in their accomplishments. The idea that randomness fundamentally shapes outcomes is hardly confined to ethics or physics; it has increasingly found a home in modern digital design. As web experiences lean into controlled chaos—from generative UI components to subtle dynamic layouts—websites are beginning to mirror the ancient philosophical observation that one cannot step into the same river twice. Each visit to a page can yield a slightly varied, living interface.
Yet, implementing controlled randomness in user interfaces has historically required dropping down into JavaScript. That dynamic is beginning to shift as web standards bodies and browser engines explore native presentational randomness directly within CSS. With Safari becoming the first browser to support the official CSS random() specification, developers are gaining access to declarative randomness. However, with cross-browser implementation still in its early stages, bridging the gap between Apple’s WebKit engine and the rest of the web ecosystem has emerged as both a technical challenge and a testing ground for experimental web features.
Real-World Use Cases for Randomness
The drive to introduce non-deterministic features into CSS stems directly from real-world product requirements. In greenfield software development and short-term consulting projects, emerging client demands often serve as an early indicator of wider industry trends. Controlled UI unpredictability is increasingly viewed as a key tool for enhancing user engagement.
A common example is the celebratory confetti effect triggered after a user completes an action, such as executing a randomized draw. While third-party JavaScript libraries for confetti abound, corporate branding requirements frequently demand meticulous control over individual particle behavior, shape, and color. In many development environments, attempts to customize off-the-shelf JavaScript plugins eventually give way to custom implementations just to ensure every randomized element adheres strictly to brand guidelines. This creates an ongoing tension in user experience design between the desire for organic chaos and the necessity of tight visual control.
To solve this, CSS specification authors have long sought to identify recurring user interface patterns and standardize them into declarative rules—a process often described within the World Wide Web Consortium (W3C) as "paving the cowpaths." Handling visual randomness entirely within the style layer aligns with core web design principles, notably the Rule of Least Power. This principle suggests that technical problems should be solved using the least powerful language capable of expressing the solution. For styling and visual presentation, standard CSS is inherently preferable to imperative JavaScript execution.
In late 2025, Safari became the first major web browser to roll out native support for the CSS random() function as part of a WebKit update focused on enabling common UI patterns through pure HTML and CSS. The addition triggered significant interest across the web development community. Front-end developers demonstrated how native CSS randomness could grant fine-grained, performant control over complex particle systems like confetti without relying on heavy JavaScript runtimes.
Despite the enthusiasm, cross-browser availability remains a major bottleneck. Half a year after Safari introduced support for random(), neither Google Chrome nor Mozilla Firefox has delivered standard implementation in stable builds. While issue trackers for Chromium and Gecko show active development and discussion around the specification, there are no firm timeline guarantees for when the feature will land across non-Apple platforms, even behind experimental browser flags.
Furthermore, the standard itself remains moving ground. The CSS random() function is currently outlined within an Editor’s Draft of the CSS Values and Units Module Level 5. Because the specification is in an early exploration phase, standard working groups explicitly warn that major breaking changes to syntax and behavior are expected. The syntax itself is intricate, featuring complex rules for random caching, keying semantics, custom scoping, step intervals, and base values.
/* Example of proposed CSS random() syntax with step intervals */
--random-size: random(1px, 7px, 1px);
Attempts to polyfill CSS features before they achieve baseline browser support are notoriously complex. Unlike JavaScript, where polyfills can seamlessly patch missing APIs in the global scope, polyfilling CSS often requires parsing stylesheets, re-fetching external resources, or introducing runtime performance overhead.
Let’s Polyfill CSS random()
The initial exclusivity of CSS random() to Safari created an unusual dynamic in front-end development. Typically, experimental web capabilities land first in Chromium-based browsers before spreading to WebKit and Gecko. The reversal left developers on Windows, Linux, and Android platforms unable to test native CSS randomness locally, while desktop Mac users could run experimental demos natively.
Even within the Apple ecosystem, native browser features do not reach all users immediately. Because Safari updates are traditionally bound to operating system upgrades, users on older macOS or iOS hardware are unable to update to the latest WebKit releases.
To bridge this availability gap, community developers have developed client-side polyfills, such as the open-source css-random-polyfill package published on npm. Designed to bring cross-browser compatibility to web demonstrations, the polyfill mimics WebKit’s implementation of CSS random() by leveraging browser-native CSS custom properties as an execution bridge.
Many of the initial test cases used to validate polyfill implementations are direct forks of demonstrations published by Apple’s WebKit engine team, which showcased early possibilities for declarative randomness during Safari Preview builds.
Demo: Random Starfield
One of the foundational demonstrations published by the WebKit team is an animated, multi-layered starfield. The visual effect consists of hundreds of scattered star elements fading in and out at randomized intervals. Larger, four-pointed stars tilt at a uniformly shared random angle, while all stars feature subtle background drop-shadows with randomized color hues.
To adapt a Safari-exclusive starfield demo for cross-browser support in Chrome and Firefox using a polyfill, the underlying markup relies on a marker class to signal which DOM elements require CSS processing, alongside a lightweight JavaScript inclusion.
<!-- The polyfill script processes custom properties on page load -->
<script src="https://unpkg.com/css-random-polyfill@latest/dist/css-random-polyfill.js"></script>
<!-- Elements designated for randomized evaluation carry a marker class -->
<div class="randomized star"></div>
<div class="randomized star"></div>
<div class="randomized star fourpointed"></div>
<div class="randomized star fourpointed"></div>
On the styling side, the polyfill architecture takes advantage of valid CSS syntax rules. Browsers naturally ignore custom property values they do not understand, but they still store valid text strings assigned to CSS variables. By assigning random() expressions to custom properties prefixed with --random, the polyfill can read the raw string declarations through the browser’s computed style engine without triggering CSS parsing errors.
.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 example highlights specific details of the CSS random() specification. For instance, the optional third parameter in random(1px, 7px, 1px) defines a step interval, forcing the engine to select only discrete integer values within the defined range.
Additionally, the spec introduces value-sharing keywords such as element-shared. This allows multiple separate elements or rules to consume the exact same calculated random seed across a render cycle:
.star.fourpointed
--random-rotation: random(element-shared, -45deg, 45deg);
rotate: var(--random-rotation);
In native WebKit implementations, random() calls can be written inline directly within standard CSS properties, functioning identically to mathematical functions like calc(), min(), or max(). The polyfill’s requirement for intermediate --random- variables represents a pragmatic compromise. Because code written using intermediate custom properties remains valid CSS under the native specification, developers can adopt the syntax today and seamlessly drop the polyfill script once cross-browser native support reaches baseline status.
Demo: Random Colored Grid Cells
Beyond atmospheric background visual effects, declarative CSS randomness allows developers to rapidly build complex, dynamic layouts. A prime structural test case involves generating a high-density layout—such as a 100×100 element CSS grid—where individual grid items occupy randomized positions and span custom grid areas.
.rectangle
--random-grid-area: random(1, var(--rows), 1) / random(1, var(--columns), 1);
grid-area: var(--random-grid-area);
This grid pattern demonstrates two crucial features of the specification: dynamic variable substitution within the random() function call itself, and the ability to combine multiple random() evaluations within a single shorthand property value (such as grid-area). The polyfill parses var(--rows) and var(--columns) at execution time, resolving the inner variables before calculating the boundary limits for the generated output.
Demo: Wheel of Fortune
A notable demonstration created by WebKit engineer Tim Nguyen illustrates how CSS random() handles complex unit conversions and physical rotation mechanics, such as a spinning interactive wheel.
@keyframes spin
from
rotate: 0deg;
to
rotate: var(--random-rotation);
#wheel
--random-rotation: random(2turn, 10turn, 20deg);
The wheel demo relies on mixed unit evaluations within a single function call: the minimum and maximum boundaries are specified in full rotations (2turn and 10turn), while the step interval parameter is defined in degrees (20deg).
The CSS Values and Units Level 5 specification permits mixing different measurement units provided they resolve to the same underlying data type category—in this case, angular dimension. This mirrors the behavior of CSS typed arithmetic in functions like calc(), where compatible units are resolved mathematically during style computation.
From an architecture perspective, current polyfill implementations process calculated styles when an element is initially rendered by the browser DOM. Consequently, dynamic mutations—such as recalculating random values inside active @keyframes loops triggered by state changes—require additional runtime evaluation listeners, such as DOM MutationObserver or dynamic style tracking hooks.
Demo: Random Squares
Simple implementations demonstrate how the specification handles property-level value linkage using custom keys. A basic example involves rendering multiple square elements, ensuring that each individual square maintains a perfectly matching width and height while varying in scale relative to other squares on the page.
--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 first caching argument inside random(), the browser engine ensures that any subsequent property referencing the --side key within the same element scope receives the identical randomized numeric value.
Chromium-Only Bonus Demo: Simulate random-item Using a Custom CSS Function
While numerical randomness covers layout positioning, dimensions, and color angles (such as hue in oklch or hsl), developers frequently need to randomly select from a discrete list of arbitrary non-numeric values, such as specific color keywords, asset URLs, or string descriptors.
To address this, the W3C specification includes a companion function proposal known as random-item(). The proposed syntax accepts caching parameters followed by a variable-length list of specific items:
/* Proposed W3C syntax for discrete item selection */
color: random-item(element-shared, red, blue, green);
While native random-item() remains largely unimplemented in current production browsers, modern Chromium builds support experimental advanced CSS features, including CSS custom functions (@function) and inline style conditionals (if()). By combining these experimental capabilities with standard CSS random(), developers can construct custom utility functions that replicate random-item() logic entirely within CSS.
--random-index: random(element-shared, 1, 5, 1);
--random-color: --item(var(--random-index), aqua, purple, pink, grey, green);
Under the hood, a custom CSS function can map an integer index argument against a collection of fallback parameters using inline 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);
);
Because CSS custom functions do not natively support variable-length rest parameters in the manner of JavaScript, optional parameters default to guaranteed empty variable tokens. The function evaluates the generated numerical index and returns the corresponding positional argument. Unlike historical community hacks designed to cycle through fixed color arrays, this declarative custom function approach operates natively across any valid CSS data type, including lengths, images, angles, and custom identifiers.
How the Polyfill Works
The core engine driving modern client-side polyfills for CSS random() relies on established open-source AST (Abstract Syntax Tree) math parsers originally constructed for build-time tooling. Specifically, the client-side polyfill adapts calculations from @csstools/css-calc, an MIT-licensed package originally designed to power PostCSS plugins.
Rather than re-parsing entire external .css files—an expensive runtime operation that often breaks under CORS restrictions—the polyfill utilizes CSS custom variables as an API layer between the browser’s native engine and client-side JavaScript.
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 polyfill script executes through a clear sequence:
First, it checks feature availability using CSS.supports(). If native support for random() is detected—as in Safari—the script terminates immediately and allows the browser engine to handle computations natively.
If support is absent, the script temporarily injects a style block setting .randomized display: none; to prevent layout thrashing or unstyled content flashes during processing. It then generates a unique session identifier via crypto.randomUUID() and initializes a WeakMap to keep track of individual DOM elements without causing memory leaks.
Next, the script queries the document for elements marked with the target class and inspects their calculated CSS properties via getComputedStyle(). It filters specifically for custom properties prefixed with --random.
For each matching property, the script passes the raw CSS string into the resolveRandom solver. Uncached random() function calls are injected with a fixed seed (Math.random()), ensuring that calls lacking explicit keying identifiers maintain unique values across evaluations.
Finally, @csstools/css-calc resolves mathematical operations, step parameters, and unit conversions into canonical CSS values. The resulting resolved string is written directly back to the element’s inline style declarations, and the temporary hiding stylesheet is removed from the document head.
By leveraging CSS custom properties as an extension mechanism, this architecture avoids the traditional pitfalls of CSS polyfills, such as stylesheet re-fetching, custom parsing bugs, and structural DOM pollution.
The Road Ahead for Native CSS Randomness
The emergence of functional polyfills offers developers an opportunity to experiment with non-deterministic UI designs today, even as full browser baseline support remains on the horizon. While native browser support across Chromium and Gecko will eventually eliminate the need for client-side scripts, the presence of polyfills allows the broader web community to stress-test the draft specification, discover edge cases, and provide feedback to standards working groups. As CSS continues to incorporate features historically reserved for imperative scripting, the boundaries of presentation-layer engineering continue to expand, bringing controlled predictability and creative chance closer together.