1

Dominando @starting-style e Transições de Display: Animações de Entrada e Saída Que Antes Só Eram Possíveis com JS

O problema que existia: display: none não aceita transição

CSS transitions funcionam interpolando valores numéricos entre dois estados. opacity: 0 para opacity: 1 gera valores intermediários (0.1, 0.2, ...). display: none para display: block não tem valor intermediário. É binário: ou o elemento existe no layout, ou não existe.

Isso criou uma dependência estrutural de JavaScript para qualquer animação de entrada e saída de elementos como modais, tooltips, dropdowns e toasts. O padrão era: adicionar uma classe .visible via JS, esperar o próximo frame com requestAnimationFrame, aplicar os estilos de transição, e na saída escutar transitionend para só então aplicar display: none.

Duas especificações CSS resolvem isso de forma nativa: @starting-style e transition-behavior: allow-discrete.

Como @starting-style funciona por baixo

@starting-style define o estado inicial de um elemento no momento em que ele entra no DOM ou passa de display: none para um valor visível. O browser usa esses valores como ponto de partida da transição, antes do primeiro frame pintado.

O fluxo interno é:

  1. O elemento recebe display: block (ou qualquer valor visível).
  2. O browser lê os valores declarados em @starting-style e aplica como estado "frame zero".
  3. No frame seguinte, o browser interpola do @starting-style para o estado final declarado nas regras normais.
  4. A transição acontece sem nenhum JavaScript.

Sem @starting-style, o browser não sabe qual é o ponto de partida. Ele simplesmente pinta o elemento no estado final, sem transição.

transition-behavior: allow-discrete

Propriedades discretas (como display e overlay) não são interpoláveis por padrão. transition-behavior: allow-discrete instrui o browser a participar da transição mesmo para essas propriedades, mantendo o valor antigo até o fim da animação (na saída) ou aplicando o novo valor no início (na entrada).

Na prática, isso significa que display: none só é aplicado depois que a transição de opacity (ou qualquer outra propriedade contínua) termina. Exatamente o que antes exigia o listener transitionend em JavaScript.

Primeiro exemplo funcional: modal com fade

/* modal.css */
.modal-backdrop {
  display: none;
  opacity: 0;
  position: fixed;
  inset: 0;
  background: oklch(0% 0 0 / 0.5);

  /* Transiciona opacity E display juntos */
  transition:
    opacity 300ms ease-out,
    display 300ms ease-out allow-discrete;

  /* Estado de entrada: o browser parte daqui quando display muda para grid */
  @starting-style {
    opacity: 0;
  }
}

/* Quando o atributo open ativa o modal */
.modal-backdrop[data-open="true"] {
  display: grid;
  place-items: center;
  opacity: 1;
}
<!-- modal.html -->
<div class="modal-backdrop" data-open="false">
  <div class="modal-content">
    <h2>Confirmar ação</h2>
    <p>Tem certeza que deseja continuar?</p>
    <button onclick="this.closest('.modal-backdrop').dataset.open = 'false'">
      Fechar
    </button>
  </div>
</div>

<button onclick="document.querySelector('.modal-backdrop').dataset.open = 'true'">
  Abrir modal
</button>

O JavaScript aqui faz uma coisa: mudar um atributo de dados. Zero lógica de animação, zero requestAnimationFrame, zero transitionend.

Animação de entrada com transform: slide + fade

/* dropdown.css */
.dropdown-menu {
  display: none;
  opacity: 0;
  transform: translateY(-8px);
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 200px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 4px;

  transition:
    opacity 200ms ease-out,
    transform 200ms ease-out,
    display 200ms ease-out allow-discrete;

  /* Ponto de partida quando o elemento se torna visível */
  @starting-style {
    opacity: 0;
    transform: translateY(-8px);
  }
}

.dropdown-trigger:focus-within + .dropdown-menu,
.dropdown-menu:has(:focus-visible) {
  display: block;
  opacity: 1;
  transfo

---

Leia o artigo completo em [https://www.vivodecodigo.com.br/react/starting-style-transicoes-display-animacoes-entrada-saida-css-1780660912588](https://www.vivodecodigo.com.br/react/starting-style-transicoes-display-animacoes-entrada-saida-css-1780660912588)
Carregando publicação patrocinada...