Animation Intermediate

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.

Old 10 lines
1.card { opacity: 0; transform: translateY(10px); }
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});
Modern
6 lines
1.card {
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.

Browser Support
83%
Chrome Firefox Safari Edge
Lines Saved
10 → 6
No JS for entry
Old Approach
Class after paint
rAF or setTimeout
Modern Approach
@starting-style
CSS-defined start state

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.

ESC