@starting-style — Animando a Entrada de Elementos em CSS Puro

A regra @starting-style define o estado inicial de uma transição CSS, permitindo animar a entrada de modais, popovers e elementos que saem de display: none sem usar JavaScript.

Animar a entrada de um elemento que estava com display: none — como um modal, um popover ou um toast — sempre exigiu JavaScript: adicionar a classe, esperar um frame para o navegador aplicar o estado inicial, depois trocar para o estado final. A regra @starting-style resolve isso em CSS puro, definindo de onde uma transição deve partir no primeiro estilo aplicado ao elemento.

O problema: por que dá pra animar a saída, mas não a entrada

Uma transição normal de CSS precisa de dois valores: o estado atual e o estado novo. Isso funciona bem para animar a saída de um elemento, porque ele já está renderizado na tela quando o novo estilo é aplicado:

.aviso {
    opacity: 1;
    transition: opacity 0.3s;
}

.aviso.saindo {
    opacity: 0;
}

Mas quando o elemento acabou de aparecer no DOM (ou saiu de display: none), o navegador não tem um "estado anterior" para animar a partir dele — o elemento simplesmente surge já no valor final, sem transição.

Como funciona o @starting-style

O bloco @starting-style define os valores que as propriedades devem assumir no instante em que o elemento recebe seu primeiro estilo — seja por ter acabado de entrar no DOM, seja por ter saído de display: none:

.aviso {
    opacity: 1;
    transition: opacity 0.3s;
}

@starting-style {
    .aviso {
        opacity: 0;
    }
}

Com isso, assim que .aviso aparece, o navegador entende que deve começar a transição em opacity: 0 e animar até opacity: 1, exatamente como aconteceria numa transição comum de saída — mas ao contrário.

Animando display: none com transition-behavior

Só o @starting-style não resolve tudo, porque display é uma propriedade discreta: ela pula direto de um valor para o outro, sem estado intermediário. Para incluir display na transição, é preciso a propriedade transition-behavior: allow-discrete, que faz o navegador trocar o valor discreto só na metade do tempo da transição, em vez de no início:

.modal {
    display: none;
    opacity: 0;
    transition: opacity 0.3s, display 0.3s allow-discrete;
}

.modal[open] {
    display: block;
    opacity: 1;
}

@starting-style {
    .modal[open] {
        opacity: 0;
    }
}

O resultado é um fade-in completo: o elemento sai de display: none, começa em opacity: 0 por causa do @starting-style e transiciona suavemente até opacity: 1, tudo sem uma linha de JavaScript.

Exemplo prático: entrada e saída de um <dialog>

O caso mais comum é justamente o elemento <dialog>, que alterna entre exibido e removido do top layer:

<dialog id="modal">
    <p>Conteúdo do modal</p>
    <button onclick="modal.close()">Fechar</button>
</dialog>

<button onclick="modal.showModal()">Abrir modal</button>
dialog {
    opacity: 0;
    transform: scale(0.9);
    transition: opacity 0.25s, transform 0.25s, display 0.25s allow-discrete, overlay 0.25s allow-discrete;
}

dialog[open] {
    opacity: 1;
    transform: scale(1);
}

@starting-style {
    dialog[open] {
        opacity: 0;
        transform: scale(0.9);
    }
}

Repare no overlay 0.25s allow-discrete: assim como display, a propriedade overlay (que controla a presença no top layer) também é discreta, e precisa do mesmo tratamento para que a transição de saída não seja cortada antes da hora.

@starting-style com múltiplos seletores

Assim como @media ou @supports, o @starting-style pode envolver vários seletores diferentes, e cada um deles define o estado inicial para o elemento correspondente:

@starting-style {
    .toast {
        opacity: 0;
        translate: 0 20px;
    }

    .badge {
        scale: 0;
    }
}

Quando usar

Cuidados

O Firefox, mesmo já suportando @starting-style, tem restrições para animar a partir de display: none em alguns cenários, então vale testar o comportamento de saída com cuidado nesse navegador. Além disso, o @starting-style só tem efeito no primeiro estilo aplicado ao elemento — se as propriedades mudarem depois por outro motivo, a transição normal (sem @starting-style) é que entra em ação.

Compatibilidade

O @starting-style e o transition-behavior: allow-discrete atingiram Baseline "amplamente disponível" com o Firefox 129, em 2024, e já contam com suporte no Chrome, Edge e Safari. Para navegadores mais antigos, vale envolver o uso em @supports (transition-behavior: allow-discrete)) e manter um fallback sem animação de entrada.

caniuse

Conclusão

O @starting-style fecha uma lacuna que existia há anos no CSS: animar a entrada de um elemento com a mesma naturalidade com que já se animava a saída. Combinado com transition-behavior: allow-discrete, elimina boa parte do JavaScript hoje usado só para coreografar o aparecimento de modais, popovers e outros elementos condicionais.

Tecnologia:
css

Deixe seu Comentário!

Dúvida, sugestão ou só um "valeu!" — tudo é bem-vindo por aqui.

Comentário
Nome
Email
lock Seu e-mail não será publicado.