per-element keyword tells the browser to treat each element independently, producing six visually distinct boxes from identical markup and a single CSS rule.
random() vs. random-item() — Know the Difference
The random() function returns a numeric value within a continuous range. The random-item() function, also part of the same specification, selects from a discrete list of values. Where random(0, 360) produces any number in that range, random-item(red, blue, green) picks one of those three keywords.
Use random() when working with numeric properties like lengths, angles, durations, or opacity. Use random-item() when selecting from a finite set of non-numeric values, such as font families, named colors, or grid-area identifiers.
random-item() has the same browser support status as random() — unimplemented in stable channels as of mid-2025.
Setting Up a Production-Ready Demo Environment
Browser Support and Feature Detection
As of mid-2025, no browser ships random() in its stable release channel. Chromium has expressed intent to implement, and both Firefox and Safari are tracking the specification. Developers planning to use random() in production should treat it as a progressive enhancement, never as a baseline requirement.
The @supports rule provides the detection mechanism:
.element {
--random-hue: 200;
background-color: hsl(var(--random-hue) 70% 60%);
}
@supports (opacity: random(0, 1)) {
.element {
background-color: hsl(random(per-element, 0, 360) 70% 60%);
}
}
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!CSS.supports('opacity', 'random(0, 1)')) {
document.querySelectorAll('.element').forEach(function(el) {
el.style.setProperty('--random-hue', Math.floor(Math.random() * 360));
});
}
});
script>
The @supports block tests whether the browser can parse a random() expression in a property value. Browsers that cannot parse it ignore the entire block, and the JavaScript fallback injects equivalent random values via custom properties. The opacity: random(0, 1) form is simpler and less likely to fail due to hsl() parsing edge cases than testing with a compound value; prefer this form for feature detection.
Project Setup
All examples in this article work with plain HTML and CSS files. No build tools, bundlers, or frameworks are required for the core demonstrations. The React integration section later provides component-level patterns for developers working in that ecosystem.
Practical Use Case 1 — Randomized Generative Backgrounds
Generative visual patterns typically require SVG, Canvas, or JavaScript to produce non-repeating aesthetics. With random(), a purely CSS-driven approach becomes viable. Randomizing HSL hue, saturation offsets, and gradient angles produces cards that each look distinct without any programmatic generation.
.card {
width: 280px;
padding: 24px;
border-radius: 12px;
color: white;
font-family: system-ui, sans-serif;
background: linear-gradient(
calc(random(per-element, 90, 270) * 1deg),
hsl(random(per-element, 180, 280) calc(random(per-element, 50, 80) * 1%) 50%),
hsl(random(per-element, 300, 360) calc(random(per-element, 60, 90) * 1%) 40%)
);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
<div class="card"><h3>Project Alphah3><p>Generative theming with zero JavaScript.p>div>
<div class="card"><h3>Project Betah3><p>Each card rendered uniquely by the browser.p>div>
<div class="card"><h3>Project Gammah3><p>Pure CSS randomized gradients in production.p>div>
The browser assigns each .card a unique gradient angle between 90 and 270 degrees, a first color stop with a hue between 180 and 280, and a second color stop with a hue between 300 and 360 — values above 360 reduce modulo 360 per spec, but clamping the upper bound to 360 ensures cross-browser consistency. Saturation ranges are constrained (50-80% and 60-90%) to keep colors vivid; saturation below roughly 50% and lightness outside the 40-60% range tend to produce washed-out or muddy output. Verify these thresholds against your specific palette.
Practical Use Case 2 — Staggered and Non-Uniform Animations
Random Animation Delays and Durations
The most common JavaScript-to-CSS bridge in production is calculating staggered animation-delay values. Developers iterate over elements, assigning incrementally larger delays. With random(), the stagger becomes non-uniform rather than linear, and no DOM traversal is needed.
<style>
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 24px;
}
.grid-item {
width: 100%;
aspect-ratio: 1;
background-color: hsl(random(per-element, 200, 260) 65% 55%);
border-radius: 8px;
opacity: 1;
animation-name: fadeIn;
animation-fill-mode: forwards;
animation-timing-function: ease-out;
animation-delay: calc(random(per-element, 0, 1500) * 1ms);
animation-duration: calc(random(per-element, 800, 2000) * 1ms);
}
@supports (opacity: random(0, 1)) {
.grid-item {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.grid-item {
animation: none;
opacity: 1;
transform: none;
}
}
style>
<div class="grid">
<div class="grid-item">div>
<div class="grid-item">div>
<div class="grid-item">div>
<div class="grid-item">div>
<div class="grid-item">div>
<div class="grid-item">div>
<div class="grid-item">div>
<div class="grid-item">div>
div>
Each grid item fades in with a delay randomly selected between 0 and 1500 milliseconds, and a duration between 800 and 2000 milliseconds. The visual effect resembles particles settling rather than the mechanical left-to-right sweep of linear stagger calculations. Elements remain visible by default; opacity: 0 is only set inside the @supports block so that browsers without random() support never hide content.
Random Transform Offsets for Particle-Like Effects
Beyond timing, random() can drive spatial offsets. Applying small random translateX, translateY, and rotate values to a set of elements produces scatter effects useful for decorative illustrations, confetti animations, or natural-looking icon distributions without a physics engine.
Practical Use Case 3 — Randomized Layout Variations
Grid and flexbox layouts typically produce uniform, predictable structures. Introducing controlled randomness into span values or gap sizes creates magazine-style visual variety.
<style>
.masonry-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 60px;
gap: 12px;
padding: 20px;
}
.masonry-item {
grid-row: span calc(round(up, random(per-element, 1, 4), 1));
background-color: hsl(random(per-element, 0, 360) 60% 70%);
border-radius: 6px;
min-height: 60px;
}
style>
<div class="masonry-grid">
<div class="masonry-item">div>
<div class="masonry-item">div>
<div class="masonry-item">div>
<div class="masonry-item">div>
<div class="masonry-item">div>
<div class="masonry-item">div>
<div class="masonry-item">div>
<div class="masonry-item">div>
div>
Each item spans between 1 and 4 rows, rounded up to the nearest integer using the CSS round() function (the up rounding strategy with a step of 1). Note that round() is itself part of CSS Values Level 4 and requires its own browser support — verify with CSS.supports('width', 'round(up, 1.5, 1)') before relying on this pattern. The span keyword requires a resolved value; whether calc(round(...)) satisfies this requirement depends on implementation. Add an @supports gate for round() and test in supporting browsers before shipping. This produces a non-uniform, masonry-like visual rhythm. Randomizing layout-affecting properties like grid spans can produce unpredictable whitespace. Test across viewport sizes, and reserve this pattern for decorative or editorial contexts rather than data-dense interfaces.
Integrating CSS random() with JavaScript and React
When You Still Need JavaScript
CSS random() accepts no seed value. There is no mechanism to produce reproducible results across page loads or across server and client environments. When you need deterministic randomness — generating a consistent visual identity from a user ID, for example — a custom seeded PRNG in JavaScript remains necessary. Common choices include mulberry32 or xoshiro128**. Math.random() itself is not seedable.
Application logic that needs to react to random values — storing a randomly chosen theme or syncing a random layout to a database — also requires JavaScript. And in server-side rendering scenarios, CSS random() is evaluated by the browser at style computation time, meaning SSR-rendered HTML contains no resolved random values. The initial server response needs fallback values.
Using CSS random() in React Components
In React applications, CSS random() is particularly valuable because it removes randomness from the render cycle. If JavaScript generates random values during rendering, those values can trigger unnecessary re-renders or produce hydration mismatches between server and client output. Because the browser never evaluates random() server-side, no resolved value appears in SSR HTML, so client hydration cannot mismatch it. Offloading visual randomness entirely to CSS avoids both issues.
Offloading visual randomness entirely to CSS avoids both issues.
import './avatar-card.css';
const ALLOWED_AVATAR_ORIGINS = ['https://cdn.example.com', 'https://avatars.example.com'];
function isAllowedUrl(url) {
try {
const parsed = new URL(url, window.location.origin);
return ALLOWED_AVATAR_ORIGINS.some((origin) => parsed.origin === origin);
} catch {
return false;
}
}
function AvatarCard({ name, role, avatarUrl }) {
const safeSrc = isAllowedUrl(avatarUrl) ? avatarUrl : '/fallback-avatar.png';
return (
<div className="avatar-card">
<img src={safeSrc} alt={name} className="avatar-img" />
<h3 className="avatar-name">{name}h3>
<p className="avatar-role">{role}p>
div>
);
}
function AvatarGrid({ users }) {
return (
<div className="avatar-grid">
{users.map((user) => (
<AvatarCard key={user.id} {...user} />
))}
div>
);
}
export default AvatarGrid;
.avatar-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 20px;
padding: 24px;
}
.avatar-card {
background-color: hsl(random(--card-hue, 0, 360) 55% 92%);
border: 2px solid hsl(random(--card-hue, 0, 360) 55% 75%);
border-radius: 12px;
padding: 20px;
text-align: center;
transform: rotate(calc(random(per-element, -3, 3) * 1deg));
}
.avatar-img {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
}
React handles data flow and rendering structure. CSS handles visual randomness. No useState, no useEffect, no random value computation during the component lifecycle. The --card-hue named caching identifier ensures the background and border share the same random hue for each card.
Hybrid Approach — JS Fallback with CSS Custom Properties
For SSR environments where browsers have not yet loaded stylesheets or where the rendering engine lacks random() support, server-side injection of random custom properties provides a fallback.
const express = require('express');
const app = express();
function esc(s) {
return String(s)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function safeNum(value, min, max) {
const n = Number(value);
if (!Number.isFinite(n) || n < min || n > max) {
throw new RangeError(`Value ${value} out of range [${min}, ${max}]`);
}
return n;
}
app.get('/cards', (req, res) => {
const cards = [
{ title: 'Card A' },
{ title: 'Card B' },
{ title: 'Card C' },
];
const html = cards.map((card) => {
const hue = safeNum(Math.floor(Math.random() * 360), 0, 360);
const rotation = safeNum((Math.random() * 6 - 3).toFixed(2), -3, 3);
const delay = safeNum(Math.floor(Math.random() * 1500), 0, 1500);
return `${hue};--fallback-rotation:${rotation}deg;--fallback-delay:${delay}ms">
${esc(card.title)}
`;
}).join('');
res.send(`
${html}
`);
});
app.listen(3000, () => console.log('Server listening on port 3000'))
.on('error', (err) => { console.error('Listen error:', err); process.exit(1); });
The associated CSS uses the custom properties as base values and overrides them inside @supports when native random() is available.
Performance and Caching Considerations
Caching Keywords and When Randomness Recalculates
The per-element caching strategy means each element receives its own random value, and that value remains stable within a single style computation pass. The value does not change on every repaint.
Caution: Any event that triggers style recomputation — toggling a class on :hover or :focus, for example — can re-evaluate random() and produce a new value, causing a visible jump. Use named caching identifiers or limit random() to static contexts to avoid this.
Named caching identifiers let multiple properties on the same element share a single random result. For instance, random(--my-size, 50px, 200px) used in both width and height ensures the element is square, with both dimensions driven by the same random number. (Note: the identifier syntax uses a dashed-ident like --my-size; verify the exact syntax against the current spec draft.)
Recalculation triggers align with style recomputation. If a class is toggled, a media query boundary is crossed, or the cascade changes in a way that forces the browser to recompute the element’s styles, the browser will re-evaluate the random value.
Performance Impact vs. JavaScript Alternatives
The rendering engine resolves random() during style computation. It doesn’t execute JavaScript on the main thread, doesn’t manipulate the DOM, and doesn’t trigger layout-invalidating style.setProperty() calls. For sets above roughly 100 elements, JS setProperty loops can exceed a single frame budget (16.7ms at 60fps); random() resolves within the existing style pass with no additional cost.
However, randomizing layout-affecting properties (widths, heights, grid spans) can cause layout thrashing if combined with other dynamic style changes. Limiting random() to cosmetic properties like colors, opacities, transforms, and animation timings is the safest approach for performance-sensitive applications.
Progressive Enhancement and Fallback Strategy
The Layered Approach
A robust production implementation uses three layers: deterministic base styles, @supports-gated random styles, and an optional JavaScript fallback.
<style>
.enhanced-card {
--card-hue: 220;
--card-delay: 0ms;
--card-duration: 600ms;
opacity: 1;
background-color: hsl(var(--card-hue) 60% 90%);
animation: fadeIn var(--card-duration) ease-out var(--card-delay) both;
border-radius: 10px;
padding: 20px;
}
@supports (opacity: random(0, 1)) {
.enhanced-card {
opacity: 0;
background-color: hsl(random(per-element, 0, 360) 60% 90%);
animation-delay: calc(random(per-element, 0, 1200) * 1ms);
animation-duration: calc(random(per-element, 500, 1500) * 1ms);
}
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
.enhanced-card {
animation: none;
opacity: 1;
}
}
style>
<script>
document.addEventListener('DOMContentLoaded', function () {
if (!CSS.supports('opacity', 'random(0, 1)')) {
document.querySelectorAll('.enhanced-card').forEach(function(card) {
card.style.setProperty('--card-hue', Math.floor(Math.random() * 360));
card.style.setProperty('--card-delay', Math.floor(Math.random() * 1200) + 'ms');
card.style.setProperty(
'--card-duration',
(Math.floor(Math.random() * 1000) + 500) + 'ms'
);
});
}
});
script>
Browsers without random() support render the base styles with a fixed hue and zero delay, with elements visible by default. If JavaScript is available, the fallback script injects randomized custom properties including duration. Browsers with full support use native random() instead, setting opacity: 0 as the animation starting state only when the animation is guaranteed to fire.
Production Implementation Checklist
Before shipping CSS random() in any production environment, verify the following:
Detection and Setup
- ✅ Verify browser support targets and add
@supportsdetection - ✅ Define deterministic base styles before layering randomness
- ✅ Choose the correct caching strategy (
per-elementvs. named identifier)
Fallbacks and Resilience
- ✅ Use
random()only for cosmetic, non-critical properties in production - ✅ Provide JavaScript fallback via CSS custom properties for unsupported browsers
- ✅ Validate SSR output includes fallback values
- ✅ Avoid
random()on layout-critical properties (widths, heights) unless intentional
Accessibility and Documentation
- ✅ Test with forced accessibility modes (high contrast, reduced motion)
- ✅ Audit randomized
animation-*properties againstprefers-reduced-motion - ✅ Document caching behavior for team members unfamiliar with the spec
Accessibility and Reduced Motion Considerations
Randomized animations must respect user preferences. The prefers-reduced-motion media query should wrap any random()-driven animation properties to prevent disorienting motion for users who have requested reduced animation.
You must scrutinize randomized color values separately. A random hue combined with fixed saturation and lightness can produce foreground/background combinations that fail WCAG contrast requirements. Constraining the lightness range and testing with tools like the Chrome DevTools contrast checker helps maintain compliance.
Cognitive accessibility is also a factor. Random layout shifts on every page load can disorient users (see WCAG 2.1 SC 2.3.1 for related guidance on motion thresholds), particularly when applied to navigation or content hierarchy. Reserve randomized layouts for decorative, non-functional contexts.
@media (prefers-reduced-motion: reduce) {
.grid-item {
animation: none;
opacity: 1;
transform: none;
}
.avatar-card {
transform: rotate(0deg);
}
.enhanced-card {
animation: none;
opacity: 1;
}
}
When the user prefers reduced motion, all randomized delays, durations, and transform offsets are neutralized. Elements appear immediately in their final state with no animation.
Should You Ship CSS random() Today?
The realistic assessment: no browser ships random() in stable channels as of mid-2025. The specification is mature and implementation signals from Chromium are positive, but production reliance on the function itself is premature.
Progressive enhancement works well here because the downside is zero. Building with random() behind @supports gates carries no runtime risk — unsupporting browsers ignore the block entirely. Browsers that support it get the enhanced experience; others get deterministic fallbacks. The JavaScript fallback layer ensures visual randomness is available across all browsers regardless of native support.
Building with
random()behind@supportsgates carries no runtime risk — unsupporting browsers ignore the block entirely.
Looking ahead, random-item() for discrete value selection is part of the same specification and will land alongside random(). Seeded randomness remains absent from the spec — there is no current proposal to add it based on available CSSWG discussions — meaning reproducible random sequences will continue to require JavaScript.
Start integrating CSS random() into non-critical visual layers now. Build the progressive enhancement scaffolding and write the fallback paths. Test the patterns against your layout constraints. When browser support arrives, the @supports block activates without a new deploy.

