The CSS @starting-style at-rule defines values from which an element can begin its first transition. Use it when an element first appears, changes from display: none to a visible value, or enters the browser's top layer.
A normal transition compares an old rendered style with a new one. A newly rendered element has no old style, so its entry transition usually cannot start. @starting-style supplies that missing starting state. It is useful for notices, menus, popovers, dialogs, and dynamically added cards.
@starting-style applies to CSS transitions. CSS animations define their own starting values with keyframes.
How @starting-style Works
The browser uses a matching starting style only when the previous style update did not produce a rendered style for the element. It compares that value with the element's regular value and starts a transition between them.
- The starting rule supplies the entry values.
- The regular rule supplies the visible destination.
- A separate hidden rule supplies the exit destination when required.
Starting declarations participate in the normal cascade. They do not permanently style the element, and they do not automatically control its later exit transition.
Syntax
You can use a standalone block containing complete rules.
Syntax:
.item {
/* Regular destination and transition */
opacity: 1;
transition: opacity 300ms;
}
@starting-style {
.item {
/* Initial entry value */
opacity: 0;
}
}
You can also nest the at-rule inside the selected rule.
Syntax:
.item {
/* Regular destination */
opacity: 1;
transition: opacity 300ms;
@starting-style {
/* Starting value for .item */
opacity: 0;
}
}
Creating an Entry Transition
This notice starts transparent and slightly below its final position. The regular rule defines where the transition ends.
Example:
.save-notice {
/* Final visible state */
opacity: 1;
translate: 0 0;
padding: 1rem;
background: #e3f7e9;
border-left: 4px solid #18864b;
transition: opacity 350ms ease, translate 350ms ease;
}
@starting-style {
.save-notice {
/* Entry state used on first rendering */
opacity: 0;
translate: 0 0.75rem;
}
}
For equal-specificity rules, place the standalone @starting-style block after the regular rule. Otherwise, the normal cascade may allow a later declaration to override the starting value. The nested form avoids repeating the selector and keeps both states together.
Entry and Exit States
@starting-style provides an entry state, not a reusable hidden state. After the element is visible, ordinary style changes transition from its current values. To animate an exit, define the destination in the element's closed or hidden rule.
An element therefore may have three states: its starting entry style, its regular visible style, and its hidden exit style. Entry and exit values may match, but CSS does not require them to match.
Using display and allow-discrete
display is a discrete property; it does not gradually interpolate. Include it in the transition and use allow-discrete when changing between display: none and a visible value.
Example:
.message {
/* Visible state */
display: block;
opacity: 1;
scale: 1;
transition: opacity 300ms, scale 300ms,
display 300ms allow-discrete;
@starting-style {
/* Entry state after display becomes visible */
opacity: 0;
scale: 0.96;
}
}
.message.is-hidden {
/* Exit state */
display: none;
opacity: 0;
scale: 0.96;
}
On entry, the browser applies the visible display value at the start so the opacity and scale transitions can be seen. On exit, it keeps the element displayed until the transition ends, then applies display: none.
Practical Popover Transition
A shown popover enters the top layer and matches :popover-open. The following minimal markup provides a declarative control; the visual behavior remains entirely in CSS.
Example:
<!-- Opens and closes the related popover -->
<button popovertarget="help-box">Show help</button>
<div id="help-box" popover>Use at least 12 characters.</div>
Example:
[popover] {
/* Closed state and exit destination */
opacity: 0;
translate: 0 -0.75rem;
padding: 1rem;
border: 0;
border-radius: 0.5rem;
box-shadow: 0 0.75rem 2rem rgb(0 0 0 / 20%);
transition: opacity 250ms, translate 250ms,
display 250ms allow-discrete,
overlay 250ms allow-discrete;
}
[popover]:popover-open {
/* Open destination */
opacity: 1;
translate: 0 0;
}
@starting-style {
[popover]:popover-open {
/* Entry state for each opening */
opacity: 0;
translate: 0 -0.75rem;
}
}
Transitioning display keeps the popover rendered during closing. Listing overlay delays its removal from the top layer. Authors cannot directly set overlay; it is used in the transition list with allow-discrete. Because a popover returns from display: none each time, its starting style can run on every opening.
Browser Support and Fallbacks
@starting-style works in current major browsers and has been broadly available since 2024. Older browsers may ignore it. Support for the at-rule does not always imply identical support for discrete display, overlay, and top-layer transitions.
Keep the regular rule usable as the fallback. Unsupported browsers should show the final content immediately. They may skip the entry effect or close an element without completing its exit transition.
Example:
@supports (transition-behavior: allow-discrete) {
.menu {
/* Add the optional discrete enhancement */
transition: opacity 200ms,
display 200ms allow-discrete;
}
}
Common Mistakes and Best Practices
- Set a non-zero duration; starting values alone create no motion.
- List every property that should transition.
- Define a separate closed state for an exit effect.
- Use
allow-discretefordisplayandoverlay. - Prefer opacity and transforms for smooth visual movement.
- Ensure the final state remains readable without the enhancement.
Respect reduced-motion preferences for decorative effects.
Example:
@media (prefers-reduced-motion: reduce) {
.save-notice,
.message,
[popover] {
/* Effectively remove decorative motion */
transition-duration: 0.01ms;
}
}
Conclusion
CSS @starting-style supplies the missing initial state for an element's entry transition. Define the visible destination in the regular rule, place initial values in @starting-style, and add a separate hidden state for exits. When display or top-layer elements are involved, combine the transition with allow-discrete and a reliable final-state fallback.