For over 10 years, transitioning elements from display: none was considered impossible in pure CSS without JavaScript reflow workarounds like requestAnimationFrame or void element.offsetWidth.

Those days are officially over! Modern CSS introduces two game-changing features (Baseline 2024 across Chrome, Safari, and Firefox):

  1. @starting-style: Defines the before-change style snapshot at frame 0 when an element transitions from display: none or mounts to the DOM.
  2. transition-behavior: allow-discrete: Delays flipping to display: none until your exit transitions finish completely.

💡 The Mental Model: The Broadway Stage Analogy

To understand why this is necessary, think of a browser render engine like a Broadway theater:

  • Without @starting-style: Toggling display: none to display: block is like an actor suddenly teleporting from backstage straight into a 100% bright spotlight. The browser cannot interpolate between "does not exist in the render tree" and "fully visible", so it abruptly snaps.
  • With @starting-style: @starting-style places the actor on their backstage floor tape mark in the dark (opacity: 0, scale: 0.85) before the curtains open. Once the stage lights go up, they smoothly fade and scale into center stage.
  • With allow-discrete: When closing, allow-discrete acts like a curtain timer. It lets the exit fade play out completely before turning off the lights and sending the element backstage.

🚀 3 Real-World Production Patterns

Pattern 1: Dropdown Menus & Modals (display: none → flex)

.modal { display: flex; opacity: 1; transform: translateY(0); transition: opacity 0.35s ease, transform 0.35s ease, display 0.35s allow-discrete; /* Frame 0 snapshot when switching from display: none */ @starting-style { opacity: 0; transform: translateY(-12px); } } .modal.is-closed { display: none; opacity: 0; transform: translateY(-12px); } 

Pattern 2: Native HTML5 <dialog> and ::backdrop Transitions

You can animate the native <dialog> element and its top-layer ::backdrop with zero JavaScript wrapper elements:

dialog { opacity: 1; transform: scale(1); transition: opacity 0.3s ease, transform 0.3s ease, overlay 0.3s allow-discrete, display 0.3s allow-discrete; &::backdrop { background-color: rgba(0, 0, 0, 0.65); transition: background-color 0.3s ease, display 0.3s allow-discrete; } } @starting-style { dialog[open] { opacity: 0; transform: scale(0.92); &::backdrop { background-color: transparent; } } } 

Pattern 3: Dynamic DOM Elements & Toast Notifications

When appending elements dynamically with JavaScript (element.appendChild), modern CSS allows them to animate on first paint automatically:

.toast { opacity: 1; transform: translateX(0); transition: opacity 0.4s ease, transform 0.4s ease; @starting-style { opacity: 0; transform: translateX(40px); } } 

🌐 Browser Compatibility (Baseline 2024)

Both @starting-style and transition-behavior: allow-discrete are part of Baseline 2024 Widely Available:

Browser Supported Version
Chrome / Edge 117+ ✅
Safari 17.5+ ✅
Firefox 129+ ✅

For older browser fallbacks, use @supports:

@supports (transition-behavior: allow-discrete) { /* Enhanced modern transitions */ } 

🎯 60-Second Viewer Coding Challenge

How would you make a modal scale in from 90% (0.9 → 1.0) on open, but slide down 20px as it closes?

  1. Grab the spoiler-free starter HTML from our GitHub Code Vault.
  2. Write your pure CSS solution (zero JS reflow hacks!).
  3. Drop your solution in the YouTube video comments — the top 3 best solutions will be pinned and inducted into our Community Hall of Fame! 🏆

📦 Resources & Links