CSS @starting-style: Animating Elements on First Render

CSS @starting-style solves a long-standing limitation: you can now animate an element’s initial appearance — when it first enters the DOM or transitions from display: none — using pure CSS transitions without JavaScript.

Problem: A CSS element — a dropdown, a modal, a tooltip — needs to animate on first render (entry animation) when it transitions from display: none to visible, but standard CSS transitions do not fire on the initial paint.

Solution: Use @starting-style to declare the CSS values the element should transition from on its first render. Define the target styles on the element and the initial styles inside @starting-style { } — the browser treats the first paint as a transition from the starting-style values.

The examples below animate a WordPress notice block fading in on page load, animate a modal dialog appearing from display: none, and use @starting-style with the new overlay transition property for top-layer elements.

/* Animate a .notice element when it's first inserted into the DOM */
.wp-block-notice {
    background: #fff3cd;
    border-left: 4px solid #ffc107;
    padding: 1rem;
    opacity: 1;
    transform: translateY(0);
    /* Declare the transition */
    transition: opacity 0.4s ease, transform 0.4s ease;
}

/* @starting-style defines the FROM state for the FIRST transition */
@starting-style {
    .wp-block-notice {
        opacity: 0;
        transform: translateY(-12px);
    }
}

/* Animate out when a .is-dismissing class is added via JS */
.wp-block-notice.is-dismissing {
    opacity: 0;
    transform: translateY(-12px);
}

Animate a dialog element (top-layer) appearing and disappearing:

/* dialog uses the top layer — needs 'overlay' in transition-property
   to hold it in the top layer until the exit animation finishes */
dialog {
    opacity: 1;
    scale: 1;
    /* Include 'overlay' and 'display' to animate top-layer elements */
    transition: opacity 0.3s, scale 0.3s, overlay 0.3s allow-discrete, display 0.3s allow-discrete;
}

/* Entry animation — FROM state */
@starting-style {
    dialog[open] {
        opacity: 0;
        scale: 0.9;
    }
}

/* Exit animation — when dialog is no longer [open] */
dialog:not([open]) {
    opacity: 0;
    scale: 0.9;
}

/* JS to open/close — no animation code needed in JavaScript */
/* document.querySelector('dialog').showModal();  → enters */
/* document.querySelector('dialog').close();      → exits  */

NOTE: @starting-style is supported in Chrome 117+, Safari 17.5+, and Firefox 129+. For unsupported browsers the element simply appears without animation — a perfect progressive enhancement. Combine it with the :popover-open pseudo-class and the Popover API for animating WordPress admin popovers.

Leave Comment

Your email address will not be published. Required fields are marked *