@starting-style

요소가 `display:none`에서 나타나는 첫 프레임의 스타일을 정의해서, 등장 애니메이션을 트랜지션만으로 만드는 규칙.

다른 이름: Entry transitionDisplay transition
···
html
<div class="wrap">
  <div class="badge" id="badge"><svg viewBox="0 0 10 10"><path id="bpath" d="M1.5 5.2l2.6 2.6L8.5 2.4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg><span id="btext">확인 중</span></div>
  <button class="chip" id="card">등장 카드</button>
</div>
css
.wrap{position:relative;width:100%;height:100%;display:grid;place-items:center}
.chip{width:180px;height:70px;border-radius:12px;border:1px solid var(--line);background:var(--surface);color:var(--fg);
  font-size:13px;font-weight:600;opacity:1;scale:1;transition:opacity .5s,scale .5s;transition-behavior:allow-discrete}
@starting-style{ .chip.enter{ opacity:0; scale:.85 } }
.chip.fallback-anim{animation:pop 2.6s ease infinite}
@keyframes pop{0%{opacity:0;scale:.85}20%,80%{opacity:1;scale:1}100%{opacity:0;scale:.85}}
.badge{position:absolute;top:10px;right:10px;display:flex;align-items:center;gap:5px;padding:4px 9px;border-radius:999px;font-size:10px;font-weight:700;background:var(--bg);border:1px solid var(--line);color:var(--accent-3);z-index:2}
.badge.no{color:var(--accent-2)}
.badge svg{width:9px;height:9px}
js
const ok = CSS.supports('transition-behavior', 'allow-discrete');
const b=document.getElementById('badge'),p=document.getElementById('bpath'),t=document.getElementById('btext');
b.classList.toggle('no', !ok);
p.setAttribute('d', ok ? 'M1.5 5.2l2.6 2.6L8.5 2.4' : 'M2 2l6 6M8 2l-6 6');
t.textContent = ok ? '@starting-style 지원됨' : '미지원 · keyframes 폴백';
const card = document.getElementById('card');
if (ok) {
  let shown = true;
  setInterval(() => {
    shown = !shown;
    if (shown) { card.style.display = 'block'; requestAnimationFrame(() => card.classList.remove('enter')); }
    else { card.classList.add('enter'); setTimeout(() => card.style.display = 'none', 500); }
  }, 2000);
} else {
  card.classList.add('fallback-anim');
}

CSS 트랜지션은 원래 "이미 화면에 있는 요소의 속성 변화"만 다뤘습니다. `display:none`이던 요소가 `display:block`으로 막 나타나는 순간에는 시작 값이라는 게 없어서, 모달이 스르륵 나타나는 연출은 keyframes 애니메이션을 쓰거나 JS로 `requestAnimationFrame` 한 프레임 뒤에 클래스를 붙이는 식의 우회가 필요했습니다.

`@starting-style` 블록 안에 정의한 값이 바로 그 "나타나기 직전" 프레임입니다. `transition-behavior: allow-discrete`와 함께 쓰면 `display`·`overlay` 같은 비연속(discrete) 속성도 트랜지션 대상에 포함되어, dialog나 popover가 `display:none`에서 페이드인하는 걸 트랜지션 한 줄로 끝낼 수 있습니다.

기본 지원은 caniuse/MDN baseline을 확인하세요. 미지원 브라우저에서는 여전히 keyframes `@keyframes`로 등장 애니메이션을 만드는 것이 표준 대안입니다.

언제 쓰나

모달·팝오버·토스트가 display:none 에서 나타날 때 keyframes 없이 부드러운 페이드인을 주고 싶을 때.