CSS @starting-style: Display None to Block Guide
The CSS @starting-style at-rule enables smooth entry
transitions for elements changing their rendering state, specifically
resolving the longstanding limitation of transitioning from
display: none to display: block. This article
explains how @starting-style defines an element's initial
pre-render state, how it interacts with the transition engine, and the
syntax required to animate entrance effects seamlessly without
JavaScript workarounds.
The Problem with
display: none
Historically, transitioning an element from
display: none to display: block made CSS
transitions fail. When an element has display: none, it
generates no layout box and has no computed style tree.
Switching the element to display: block forces the
browser to calculate its initial styles and final styles simultaneously
in a single render frame. Because there was no previous frame or
baseline style to interpolate from, the element would snap instantly to
its visible state, ignoring any specified transition rules
for properties like opacity or transform.
How
@starting-style Solves the Issue
The @starting-style rule establishes a "before-open" or
baseline state. It provides the browser with the computed values an
element should have right before its first style update or at the exact
moment its display changes from none to any
rendered value (block, flex,
grid, etc.).
When the browser detects a change from display: none to
display: block:
- It applies the styles inside
@starting-styleas the initial snapshot. - It immediately queues the target styles defined on the element.
- It interpolates between the
@starting-stylevalues and the element's actual styles over the declared transition duration.
The Role of
transition-behavior: allow-discrete
While @starting-style defines the starting visual
properties (like opacity: 0), transitioning
display itself requires modern discrete animation
handling.
By default, the display property cannot be transitioned.
To allow an element to remain in the render tree
(display: block) during both entrance and exit animations,
you must transition the display property using
transition-behavior: allow-discrete.
When combined:
- On entering (
display: block): The element switches todisplay: blockimmediately, applies@starting-stylevalues, and transitions to the target properties. - On exiting (
display: none): The element transitions its visual properties first, delaying the switch todisplay: noneuntil the animation ends.
Implementation Example
.card {
/* Final rendered state */
display: block;
opacity: 1;
transform: translateY(0);
/* Enable transitions on both standard and discrete properties */
transition:
opacity 0.4s ease,
transform 0.4s ease,
display 0.4s allow-discrete;
}
/* Initial state when switching away from display: none */
@starting-style {
.card {
opacity: 0;
transform: translateY(-20px);
}
}
/* Hidden state */
.card.is-hidden {
display: none;
opacity: 0;
transform: translateY(-20px);
}Nesting
@starting-style
@starting-style can also be nested directly within the
component selector using modern CSS nesting rules:
.modal {
display: flex;
opacity: 1;
transform: scale(1);
transition: opacity 0.3s ease, transform 0.3s ease, display 0.3s allow-discrete;
@starting-style {
opacity: 0;
transform: scale(0.9);
}
&.is-closed {
display: none;
opacity: 0;
transform: scale(0.9);
}
}Key Rules to Remember
- First Render Only:
@starting-styleonly takes effect when an element is first rendered to the DOM or when itsdisplaytoggles out ofnone. It has no impact on normal property updates once the element is already rendered. - Pair with Target Styles: The properties declared
inside
@starting-stylemust match the properties being animated on the target element. - Discrete Transitions: Always include
displayin yourtransition-propertydeclaration withallow-discreteto ensure entrance and exit cycles operate symmetrically without snapping.