Assinar
Voltar
Frontend

O random() do CSS trocou de significado entre dois Safaris. O seu CSS continua válido

O Safari 26.2 foi o primeiro a entregar random(). O 26.5 mudou o que um nome significa: de escopo por elemento para global. Mesma sintaxe, nenhum aviso, outro resultado na tela.

</>

Em dezembro de 2025 o Safari 26.2 se tornou o primeiro navegador a entregar a função random() do CSS. Em maio de 2026 o Safari 26.5 mudou o que ela significa. O CSS que você escreveu na primeira versão continua válido na segunda: mesma sintaxe, nenhum erro de parse, nenhum aviso no console. Só o resultado na tela é outro.

A posição deste post: o difícil no random() nunca foi sortear um número. É decidir quantos sorteios existem. Esse é o trabalho do primeiro argumento, o <random-key>, e é exatamente onde o grupo de trabalho mexeu depois de o recurso já estar em produção. Quem for adotar agora precisa entender o argumento que quase todo exemplo omite.

A parte fácil primeiro

A especificação, na CSS Values and Units Level 5, define a função assim:

<random()> = random( <random-key>? , <calc-sum>, <calc-sum>, <calc-sum>? )

Os dois <calc-sum> obrigatórios são o mínimo e o máximo, ambos inclusivos, com distribuição uniforme. Se o máximo for menor que o mínimo, a função se comporta como se ele fosse igual ao mínimo. O terceiro, opcional, é o passo: os valores possíveis passam a ser da forma min + (N * step), com N inteiro não negativo sorteado uniformemente entre os que resultam em valor dentro do intervalo.

Duas sutilezas do passo que valem guardar. A primeira: o máximo pode não estar entre os resultados possíveis. Em random(100px, 200px, 30px) os valores são 100px, 130px, 160px e 190px, e 200px nunca sai. A segunda, mais interessante, é que passo não é a mesma coisa que arredondar depois. Em random(100px, 200px, 50px) os três valores possíveis têm 1/3 de chance cada. Em round(random(100px, 200px), 50px) os mesmos três valores aparecem, mas 150px sai metade das vezes e 100px e 200px saem um quarto cada.

Os argumentos precisam ter tipo consistente. random(50px, 100%, 1em) é válido quando a porcentagem resolve para comprimento naquele contexto. random(50px, 180deg) é inválido.

A parte que quebrou

CSS é declarativo. O navegador pode avaliar uma função na ordem que quiser, quantas vezes quiser, porque toda execução devolve o mesmo valor. Função aleatória não cabe nesse contrato: ela é inerentemente com estado e se importa com quando, com que frequência e em que ordem foi executada.

A saída da especificação é dar a cada função aleatória um nome de cache. Duas funções com o mesmo nome devolvem o mesmo valor base; nomes diferentes devolvem valores sem relação. O <random-key> é quem controla isso:

<random-key> = auto | <random-cache-key> | fixed <number [0,1]>
<random-cache-key> = <dashed-ident> || element-scoped
                     || [ property-scoped | property-index-scoped | <random-ua-ident> ]

Omitir o argumento equivale a auto, que é o mais aleatório possível: o valor varia entre instâncias dentro do mesmo valor, entre propriedades e entre elementos. A especificação diz que auto é equivalente a escrever element-scoped property-index-scoped.

Os pedaços que se combinam para formar o nome de cache:

  • <dashed-ident>, como --s, dá um nome explícito ao valor.
  • element-scoped acrescenta um identificador do elemento ao nome, então elementos diferentes recebem valores diferentes.
  • property-scoped acrescenta o nome da propriedade, então propriedades diferentes recebem valores diferentes.
  • property-index-scoped acrescenta o nome da propriedade e o índice da função entre as funções aleatórias do mesmo valor, então duas instâncias na mesma declaração recebem valores diferentes.

Agora o que mudou. O post de lançamento do Safari 26.5, assinado por Jen Simmons, registra o fato: depois do 26.2 o grupo de trabalho ajustou como valores nomeados funcionam. Usar um nome, como random(--size, 100px, 200px), passou a produzir um resultado global, em vez de algo com escopo por elemento. O 26.5 implementou a mudança e acrescentou a palavra-chave element-scoped para quem precisa do comportamento por elemento.

O mesmo CSS, dois resultados

O exemplo do próprio post da WebKit, com oito elementos .box:

.box {
  width: random(--s, 100px, 200px);
  height: random(--s, 100px, 200px);
}

No Safari 26.2 isso dava oito quadrados de tamanhos diferentes: o nome amarrava largura e altura dentro de cada caixa, e cada caixa sorteava o seu. No Safari 26.5 isso dá um tamanho aleatório para as oito caixas, porque o nome agora é global.

Para recuperar o comportamento antigo é preciso escrever o escopo:

.box {
  width: random(--s element-scoped, 100px, 200px);
  height: random(--s element-scoped, 100px, 200px);
}

A ordem dos dois pedaços não importa, então random(element-scoped --s, 100px, 200px) também vale. E o post registra a remoção: a palavra-chave element-shared saiu do Safari 26.5, porque o novo comportamento padrão cobre o caso de uso e o grupo de trabalho tirou a palavra da especificação.

Vale ser justo com a WebKit aqui. Implementar cedo é o que faz especificação amadurecer, e boa parte dos ajustes só aparece quando alguém constrói a coisa. O que o episódio ensina não é sobre quem errou, é sobre o custo de adotar uma função que ainda está em Working Draft: a nota de versão vira leitura obrigatória, porque o compilador não vai te avisar.

random-item(), e por que a chave é obrigatória lá

A irmã da função escolhe um item de uma lista:

<random-item()> = random-item( <random-key> , [ <declaration-value>? ]# )

Aqui a chave é obrigatória, e a especificação explica o motivo: seria impossível saber se random-item(--foo, --bar, --baz) tem três valores ou dois valores e uma chave de cache.

A diferença de mecânica importa. O random-item() é uma função de substituição arbitrária, como o var(). Isso quer dizer que a propriedade é assumida válida no momento do parse, que a substituição acontece em tempo de valor computado, e que os filhos herdam o mesmo valor resolvido. Se o valor substituído deixar a propriedade inválida, ela vira o valor garantidamente inválido.

Onde isso está hoje

Safari entregou, com a semântica corrigida a partir do 26.5. No Chrome, o registro no Chrome Platform Status marca a função como Proposed, com o marco 155 anotado para desktop e Android, sem flag e sem origin trial, e com o rastreamento no issue 413385732 do Chromium. A Mozilla registrou posição Positive na issue 809 do repositório de standards positions, que é sinal de intenção, não de implementação. A especificação segue como rascunho de trabalho.

Existe polyfill circulando, e ele funciona dentro de um recorte: processa propriedades customizadas com prefixo convencionado no carregamento da página. O que ele não faz é justamente a parte que dá trabalho, porque roda uma vez e não acompanha mudança de DOM nem de estilo.

A recomendação prática é a de sempre para recurso em Working Draft com uma implementação só: use como enriquecimento, nunca como estrutura. Espalhar partícula de fundo, variar a rotação de uma pilha de cartões, quebrar a regularidade de um padrão decorativo. Nada que, faltando, deixe a página sem layout. E se você escreveu random() com nome entre dezembro e maio, releia esse CSS: ele não está quebrado, ele está fazendo outra coisa.

Perguntas frequentes

O valor é sorteado de novo a cada quadro de animação?

Não. O modelo da especificação é de valor base por nome de cache: toda avaliação com a mesma chave devolve o mesmo valor base. É justamente esse mecanismo que faz a função caber num modelo declarativo.

Para que serve o fixed?

O fixed <number [0,1]> ignora o nome de cache e usa o número direto como valor base. A especificação diz que ele é útil para o autor obter valor previsível enquanto testa, mas que existe mesmo para resolver casos de canto de herança.

Dá para usar em qualquer propriedade?

O random() é função matemática, então vale onde o tipo resultante vale. O random-item() é substituição arbitrária, e nesse caso os argumentos são sequências arbitrárias de valores CSS.

E a acessibilidade?

Aleatoriedade em movimento é o ponto de atenção. Se o valor sorteado alimenta animação, respeite prefers-reduced-motion como você já faria. Aleatoriedade em tamanho e cor pede o cuidado de sempre com contraste: intervalo mal escolhido pode sortear uma combinação ilegível, e ninguém revisa o que só aparece em um carregamento a cada cem.

Fontes

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *