CSS & layout

CSS random() puts a little deliberate mess back into your layout

CSS random() puts a little deliberate mess back into your layout

If your "hand-made" look is a Sass loop spitting out forty :nth-child rules with hard-coded rotations, I've got bad news. That was never randomness. It was a lookup table in a fake mustache. (Everybody shipped one. Mine had a diff you could see from orbit.)

Brecht De Ruyte's new piece in Smashing Magazine is about what replaces it: the native random() function from the CSS Values and Units Level 5 spec. According to his article, Safari landed it first, Chrome 156 is rolling it out right now, and Mozilla has sent positive signals about Firefox. The syntax isn't actually the best part. De Ruyte builds design systems for a living and says so up front ("Consistency is usually our bread and butter as design engineers"), and then he goes looking for the few places where a bit of mess is the right call.

The arguments, one at a time

Here's the grammar he quotes from the spec:

random( <random-caching-key>?, <min>, <max> [, by <step>]? )

In practice he writes plain comma-separated arguments:

rotate: random(-3deg, 3deg);
inline-size: random(20px, 80px, 10px);
animation-duration: random(1.5s, 3s, 0.2s);

The min and max have to share the exact same unit type. random(-4deg, 4deg) is fine, and so is the unitless random(0.3, 1). random(2rem, 100px) is invalid. If you need to mix units, De Ruyte says to put calc() inside the arguments.

The third argument is the step. Without it the browser picks from a continuous range. With it, the value rounds to discrete increments, so top: random(0px, 60px, 15px) only ever lands on 0, 15, 30, 45 or 60px. That's jitter that still respects your grid.

Then there's the caching key, the one piece that needs a real explanation. By default, every random() on a property rolls its own independent value. Randomize inline-size and block-size separately and your "random square" comes out as a random rectangle. Give both declarations the same key and the engine shares the computed value between them:

.card-thumbnail {
  inline-size: random(--square-size, 60px, 120px);
  block-size: random(--square-size, 60px, 120px);
}

Quick tangent, because this tripped me up. --square-size looks exactly like a custom property, but nothing in that snippet ever declares it. It doesn't hold a value. It's a name the two declarations share. OK, back on track.

Wobbly type, bouncing letters, a fanned-out deck

De Ruyte's first demo splits a word into .char spans and gives each letter a tiny tilt, a nudge and a little ink variance:

.char {
  display: inline-block;
  rotate: random(-2.8deg, 2.8deg);
  translate: random(-1.5px, 1.5px) random(-2.8px, 2.8px);
  opacity: random(0.8, 1, 0.02);
}

Hover the word and a .word:hover .char rule resets every letter to straight. Honestly, the first time I watched letterforms wobble with zero JavaScript involved, I felt the way I did the first time :hover worked on something that wasn't a link. A small thing, and it made me grin.

The second demo feeds random() into custom properties (--jump, --dur and a negative --delay), so every letter is already mid-bounce on page load and no two move in sync. The splayed-card demo is the one I'd steal. sibling-index() and sibling-count() compute the fan structure, random() adds jitter on top, and animation-timeline: scroll(root) drives it as you scroll. Math sets the layout, and randomness only nudges it. Then he closes on a punk fanzine called "Never mind the defaults", which, as someone whose teens were scored by drop-D riffs, I respect.

Where it stays out

His no-go list is the part to print out. No randomness in core layouts and grids (column gutters, form field widths, container padding). None in body copy (font sizes, line heights, letter spacing on long paragraphs). And no tilted or shifted buttons and links unless they reset on hover and focus. In his words: "If a user has to chase a moving or crooked button, you've failed them."

There's also the accessibility homework. Split a word into spans and screen readers may read it out letter by letter, so his markup puts aria-label on the h1 and aria-hidden="true" on the wrapper span. Under prefers-reduced-motion: reduce he removes the animation and also flattens rotate and translate. His reasoning is that a lot of random angles can bother people while they scroll, not just the motion.

Using it before every engine has it

Because it's still rolling out, De Ruyte wraps the enhancement in @supports and keeps a straight default:

.badge {
  rotate: 0deg;
}

@supports (rotate: random(-2deg, 2deg)) {
  .badge {
    rotate: random(-2.5deg, 2.5deg);
  }
}

He also points to random-item(), which is in the same spec and picks from a list instead of a range (random design tokens, say). He notes that no browser supports it yet.

Verdict: your tokens set the structure, and the engine adds a few degrees of controlled chaos. That's an 8/10, and it only drops two points because one engine still hasn't shipped it.

Source: Smashing Magazine (smashingmagazine.com)

Related
CSS & layout

Your icon is centered against the wrong thing

Ahmad Shadeed's new piece nails the icon-and-label bug that has been quietly wrong on every design system list for years, and the fix is one transform in the lh unit.

September 16, 2026
CSS & layout

A polyfill for CSS random(), while the browsers catch up

Lee Meyer's walkthrough on CSS-Tricks builds a polyfill for the emergent CSS random() function so authors can start using it — and get fluent in its caching keywords — before every engine ships.

September 9, 2026
CSS & layout

The lh unit sizes to the line, not the font

Ahmad Shadeed's post on ishadeed.com tours the CSS lh unit, which resolves to the element's computed line-height. Here's where sizing against that line beats sizing against font-size.

August 1, 2026