CSS random() function makes JavaScript and Sass unnecessary for random effects
The random() function, from CSS Values and Units Level 5, already runs in Safari and is coming to Chrome 156: you can tilt letters, cards, and shadows directly in the stylesheet, without a preprocessor loop or a JS library.

Perfectly aligned interfaces have a side effect: they feel lifeless. Tight grids, rigid spacing tokens, and pixel-identical typography guarantee predictability, but it's exactly that predictability that makes so many screens look like they came out of the same mold of AI-generated design without direction. Front-end developer Brecht De Ruyte, from Belgium, raises this discomfort in an article for Smashing Magazine and uses CSS's new native random() function to test where controlled imperfection fits in a design system without turning into a mess.
The angle matters to anyone designing product because it solves an old friction point: until now, any "random" effect on the front end (slightly crooked cards, letters with subtle variation, bokeh-style blur) required generating hundreds of lines of static CSS in a Sass loop, or adding a JavaScript dependency just to calculate an angle or an animation duration. With random() directly in the stylesheet, that calculation becomes built into the language itself.
How the function works in practice
The formal specification is random(<random-caching-key>?, <min>, <max> [, by <step>]?), but in actual use the arguments go in directly, separated by commas:
/* Random angle between -3deg and 3deg */
rotate: random(-3deg, 3deg);
/* Random width between 20px and 80px, in steps of 10px */
inline-size: random(20px, 80px, 10px);Two rules matter for anyone who works with CSS day to day. First, minimum and maximum need to be in the same unit (you can't mix rem with px directly; you need to resolve that with calc() first). Second, the optional third argument (the step) rounds the result to fixed increments: random(0px, 60px, 15px) only produces 0px, 15px, 30px, 45px, or 60px, useful when the variation needs to fall on a grid.
The feature most relevant to anyone thinking in terms of systems, and not isolated effects, is the caching key. By default, every time random() is evaluated on a property, it generates an independent value. That breaks anything that needs two synchronized values, like the width and height of a card that needs to stay square:
.card-thumbnail {
inline-size: random(--square-size, 60px, 120px);
block-size: random(--square-size, 60px, 120px);
}Since both declarations reference the same --square-size key, the browser shares the computed value between them. Without the key, the card turns into a crooked rectangle on every refresh; with it, the square stays perfect, only changing size.
The experiments that show where this fits
To test the limits of the feature, De Ruyte built a series of demos on CodePen that work as a reference catalog for anyone deciding whether random() belongs in a real project:
- Imperfect typography: each letter of a word gets slightly different rotation, offset, and opacity, mimicking the irregularity of old-school letterpress printing; on hover, everything returns to perfect alignment via transition.
- Procedural bounce: letters bouncing with unique height, duration, and animation delay for each character, using negative
animation-delayso all of them are already in motion when the page loads. - Tilted cards: random
--tiltand--shift-y, with the shadow calculated as a function of the tilt angle itself, so the card looks physically resting on the table. - Fanned deck of cards: combines
sibling-index()(deterministic math, so the fan opens the same way no matter how many cards exist) withrandom()(the human imperfection of whoever shuffled the deck), all tied toanimation-timeline: scroll(root). - Marquee speed:
random(15%, 44%)sets the speed of each line of text sliding across the screen, with no JavaScript calculating transforms on scroll.
Randomness in CSS doesn't have to mean throwing your design system out the window. It's about little imperfections and playfulness. Maybe let's even call it fun.
Brecht De Ruyte, front-end developer
What this replaces: preprocessor loops and JS libraries
Before random(), simulating that same inconsistency required one of two paths. One was writing a Sass loop that generated, at build time, dozens or hundreds of .char-1, .char-2, .char-3 classes with fixed, pre-calculated values: it solves the effect, but bloats the final CSS and locks the number of variations in at build time. The other was adding a JavaScript library (or writing Math.random() by hand) to compute the angle at runtime and inject it as inline style, which adds bundle weight and one more piece of JS running just for an aesthetic detail.
With native random(), that calculation happens right in the browser's rendering engine, with no build step and no JS runtime. For anyone deciding front-end architecture, that means fewer dependencies to maintain, less statically generated CSS, and the ability to re-evaluate the value on every repaint, something no preprocessor loop could do.
The care that separates a cool effect from broken accessibility
Breaking a word into one <span> per letter to style each character individually has a serious side effect: screen readers often spell it out, reading "i - m - p - e - r - f - e - c - t - i - o - n" instead of the whole word, or pausing oddly between each letter. It's the kind of problem that only shows up when someone tests with assistive technology, not just by looking at the screen.
The fix is simple and should be part of the definition of done for any component using this pattern: the visual spans get marked with aria-hidden="true", and the parent element receives an aria-label with the full, clean word. Screen reader users hear the word normally; sighted users see the typographic variation. It's not an optional detail, it's part of what makes the effect publishable.
When random() isn't the answer
De Ruyte himself acknowledges that most of a design engineer's work is the opposite of this: consistent spacing, locked-down typography, aligned grids. A dashboard card that jumps a few pixels on every refresh isn't creative expression, it's a bug someone will report. Random() makes sense in specific contexts:
| Context | Does Random() help? |
|---|---|
| Dashboard, data panel, enterprise interface | No: consistency is the requirement |
| Portfolio, lifestyle product site, editorial landing page | Yes: small imperfection reinforces the human feel |
| Brand component with tonal variation (zine, collectible card) | Yes: random-item() (still unsupported) will allow picking among color tokens |
| Any piece tested with a screen reader | Only with correct aria-hidden + aria-label |
On browser support: Safari was the first to ship random(), Chrome is rolling it out in version 156, and Mozilla has already signaled positive intent to implement it in Firefox, with no confirmed date. While the rollout isn't complete, the recommendation is to wrap the usage in @supports (rotate: random(-2deg, 2deg)), with a fixed value as a baseline outside the block.
Still pending is random-item(), another function from the same specification, which would allow picking among a list of tokens (colors from a palette, for example) instead of a numeric range. For now, no browser implements this variant, which limits randomization to continuous values or ones discretized by step, not choices among named tokens.
Translated from the Brazilian Portuguese original · Read the original
Figma Dev Mode MCP Server connects AI agents directly to the design file
Figma's MCP server lets Copilot, Claude, and Cursor pull variables, components, and specs directly from the design file. The promise is to end manual handoff, but the result depends on how organized the design system is.




