Skip to main content
Photography of Alvaro Montoro being a doofus
Alvaro Montoro

Human Being

Colorful dice

Native Random Values in CSS

The CSS Working Group has published the Values and Units Module Level 5, which introduces native mechanisms for generating random content using only CSS. This is the tl;dr of a longer article exploring randomness in CSS.

css html webdev javascript

CSS has always been a deterministic language: given the same input, you get the same output. Every time. But that's about to change with the introduction of two new random functions in CSS.

Note: At the time of writing this article, support for this feature is not widespread (only Safari offers partial support for the random() function from version 26.2). This is a review of the specification and the features that are coming.

This article dives straight into the feature. For a more thorough review —including the history of randomization in CSS, how the feature works, and what it means for CSS as a language—, check out my upcoming article, which complements a conference talk I gave recently.

Let's explore the new functions!

random()

random() returns a random value within a specified range. Its simplest form takes two parameters —a minimum and a maximum value—, and produces a result anywhere within that interval.

For example:

div {
  width: random(200px, 500px);
}

/*
Possible outputs:
- width: 230px;
- width: 417px;
- width: 308.342px;
*/

Notice how the resulting values aren't limited to whole numbers. Anything within the range is valid, including decimals.

Incremental Random

But sometimes we don't want decimals (who enjoys dealing with half?pixels in a layout?), and the good news is that the function accepts an optional third parameter to define the step or increment applied to the random value.

To do that, we use the by [value] indicator at the end of the function:

div {
  rotate: random(0deg, 180deg, by 10deg);
}

/*
Possible outputs:
- rotate: 120deg;
- rotate: 40deg;
- rotate: 180deg;

Not possible:
- rotate: 5deg;
- rotate: 134deg;
- rotate: 89deg;
*/

The increments will start counting from the min value, which means that sometimes the max value may not be an option. For example, random(100, 200, by 30) will return 100, 130, 160, or 190; but not 200 (it cannot be reached in increments of 30 from 100) or 210 (out of bounds).

Caching Options

The random() function has an interesting behavior that is worth discussing. Consider the following code:

div {
  width: random(10em, 30em);
  height: random(10em, 30em);
}

/*
Possible outputs:
- width: 14em; height: 14em;
- width: 21.5em; height: 21.5em;
- width: 27em; height: 27em;
*/

Notice how the outputs for width and height are the same each time the page is loaded and random() is invoked. This happens because, in order to reduce the function's complexity and simplify the browser's work, random() will always return the same value when all the provided parameters are identical. In this case, both width and height get the same result because they call the function with a minimum of 10em and a maximum of 30em.

To avoid this behavior, a new optional parameter was introduced: a caching option. It is a custom property (aka CSS variable) that is passed as the first parameter and serves two purposes:

  1. It stores the value produced by the function.
  2. It helps break the uniformity of the parameters.

By using different caching options, we get different values because the function parameters are no longer identical:

div {
  width: random(--w, 10em, 30em);
  height: random(--h, 10em, 30em);
}

/*
Possible outputs:
- width: 12em; height: 18em;
- width: 21em; height: 21em;
- width: 23em; height: 17em;
*/

Individual Randomness

So far, the generated values have been shared by all elements selected by the rule. But what happens if we want different random values for each one? We could write some overcomplicated code... or we could use the per-element keyword introduced in the specification.

The per-element keyword tells the browser that the random() function must be evaluated individually for each element, rather than once at the rule level.

div {
  width: random(per-element, 10%, 50%);
  height: random(per-element, 100px, 500px);
}

/*
Possible outputs:
div#1 - width: 12%; height: 100px;
div#2 - width: 45%; height: 364px;
div#3 - width: 34%; height: 474px;
*/

Performance note: using per-element on a large number of elements may increase layout computational time and decrease performance. Take that into account when using it.


With this, we have reviewed the different syntax forms and options of the random() function. Next, let's explore the second function introduced as part of the CSS Values and Units Module Level 5 specification.

random-item()

CSS includes properties with discrete values that cannot be expressed as a range. In those cases, random() is not particularly helpful. Instead, we need a function that takes a list of possible values and randomly selects one... and that is exactly what random-item() does.

The random-item() function takes a caching option and a series of values as parameters, then randomly selects and returns one value from the list:

div {
  display: random-item(--d, block, flex, grid, table);
  opacity: random-item(--o, 0.5, 0.6, 0.75, 0.9, 1);
  background: random-item(--b, red, #00f, conic-gradient(#fff 0 0));
}

/*
Possible outputs:
- display: block; opacity: 0.9; background: #00f;
- display: grid; opacity: 0.6; background: red;
- display: flex; opacity: 0.75; background: red;
*/

One important detail: in random-item(), the caching variable is not optional, it is required.

As a small curiosity, something some readers may already know, but that I discovered while reviewing this specification (though it likely originates from a different one): CSS allows the use of curly braces ({}) to delimit a list of comma-separated values when that list is used as a single value within a comma-separated list of parameters. Basically, this is only necessary when passing nested lists as parameters:

div {
  font-family: random({ Times, serif }, { Arial, sans-serif }, monospace);
}

/*
Potential outputs:
- Times, serif 
- Arial, sans-serif
- monospace
*/

Conclusion

I hope this article was insightful and that you learned about the upcoming random() and random-item() functions in CSS (already here if you are using Safari). It ended up being longer than expected, but I think it was worth covering all the options and explaining them in detail with examples.

Remember to consider browser support in CSS, caching, and performance implications when applying these functions in production. Also, think about practical use cases such as random colors, rotations, or spacing, which can enhance the visual experience without JavaScript.

Some may question if adding these functions at this level is a good idea at all. From an architectural point of view, they make sense: this is a layout concern, and it should be handled in the layout layer (CSS) rather than the logic layer (JavaScript), as has been the case so far.

...But more on that in the longer article coming up soon!

Article originally published on