Entry animations without JavaScript timing
Transitions only ran when a value changed. To animate in, you added a class in JS after paint. @starting-style defines the before state so CSS can transition from it.
2.card.visible { opacity: 1; transform: none; }
3// JS: must run after paint or transition won't run
4requestAnimationFrame(() => {
5 requestAnimationFrame(() => {
6 el.classList.add('visible');
7 });
8});
2 transition: opacity .3s, transform .3s;
3 @starting-style {
4 opacity: 0;
5 transform: translateY(10px);
6 }
7}
No JS timing
No double rAF or setTimeout. The browser knows the starting state from CSS.
Declarative
Entry and transition live in one place. Works with dynamic content and frameworks.
Same transition
Uses your existing transition properties. No separate keyframes for enter.
How it works
CSS transitions only run when a property changes. If an element appears with opacity: 0 and you want it to transition to 1, the browser never saw the 0, so you had to set the initial state, then add a class in the next frame (requestAnimationFrame, or double rAF) so the change was detected.
@starting-style lets you define the style that applies at the moment the element is first rendered. The browser uses that as the from state and transitions to the element's actual styles. No JavaScript, no timing hacks.