Home / Articles / Animation & Motion /

New Featureanimation

Entry and exit transitions in CSS

Master element appearance and disappearance animations. Full enter/exit transitions with @starting-style and transition-behavior.

The challenge of animating element creation

Traditional CSS transitions work on state changes: hover to not-hover, active to inactive. But what about an element that didn't exist before? An item inserted into the DOM, or a modal that flips from display: none to visible? Historically, this required JavaScript to stagger class additions or frame-by-frame manipulation. The browser couldn't transition from nothing to something.

Two modern CSS features solve this: @starting-style defines the initial state of newly visible elements, while transition-behavior: allow-discrete lets discrete properties like display participate in transitions. Together, they enable full entry-and-exit animations without JavaScript.

Entry animations: @starting-style

When an element first becomes visible, @starting-style sets its initial state on that first frame. The browser then transitions from those values to the computed style:

.fade-in {
  opacity: 1;
  translate: 0 0;
  transition: opacity 0.4s ease, translate 0.4s cubic-bezier(0.2, 0, 0, 1);

  @starting-style {
    opacity: 0;
    translate: 0 1rem;
  }
}

/* Trigger by adding/removing class */
.hidden {
  display: none;
}

Removal of the .hidden class makes the element visible. The browser uses the @starting-style values as the starting point, creating a smooth entrance from opacity 0 and translate Y 1rem.

Elements in the @starting-style block should match the final hidden state. The browser transitions from those values to the element's normal state.

Exit animations: transition-behavior and display

The reverse problem: animating to display: none without cutting the animation short. Without allow-discrete, adding a class that sets display: none instantly removes the element. Add it instead:

.modal {
  opacity: 1;
  scale: 1;
  display: block;
  transition:
    opacity 0.3s ease,
    scale 0.3s ease,
    display 0.3s allow-discrete;

  @starting-style {
    opacity: 0;
    scale: 0.95;
  }
}

.modal.is-closed {
  opacity: 0;
  scale: 0.9;
  display: none;
}

When .is-closed is added, all properties transition together. The display transition holds block visible, then flips to none only after the opacity and scale animations finish. The user sees a complete exit animation followed by the element being removed from the layout.

The complete pattern: bidirectional transitions

The same CSS handles both directions. Add a state class for exit, remove it for entry. The @starting-style block is always the entry animation; the base-to-state transition is always the exit animation:

.dropdown {
  opacity: 1;
  translate: 0 0;
  display: block;
  transition:
    opacity 0.2s ease,
    translate 0.2s ease,
    display 0.2s allow-discrete;

  @starting-style {
    opacity: 0;
    translate: 0 0.5rem;
  }
}

.dropdown.is-hidden {
  opacity: 0;
  translate: 0 -0.5rem;
  display: none;
}

/* HTML usage */




This is the same pattern used for toast notifications, accordions, dropdowns, and modals. The CSS is identical regardless of the component.

Top layer elements: the overlay property

Dialogs and popovers exist in the top layer, a special stacking context. When animating these elements out, the overlay property must be included in the transition list. Without it, the element drops out of the top layer the instant it's hidden, making the exit animation invisible:

[popover] {
  opacity: 1;
  translate: 0 0;
  transition:
    opacity 0.25s ease,
    translate 0.25s ease,
    display 0.25s allow-discrete,
    overlay 0.25s allow-discrete;

  @starting-style {
    opacity: 0;
    translate: 0 0.75rem;
  }
}

/* Hide via attribute or class */
[popover]:not(:popover-open) {
  opacity: 0;
  translate: 0 -0.75rem;
}

This pattern works with the native popover API: open with showPopover(), close with hidePopover() or popovertarget. The animations are purely CSS.

Practical considerations

Durations matter for UX. 200-300ms is the sweet spot for most UI transitions: fast enough to feel responsive, slow enough to be noticeable. For larger, more dramatic transitions (full-screen modals, page transitions), 400-500ms works. Use easing that matches the motion: ease-out for entrances (starts fast, slows down), ease-in for exits (starts slow, finishes fast).

Always test with keyboard navigation. Animated elements should still follow the correct tab order, and the animations shouldn't interfere with focused states. The display: none approach is better than opacity: 0 for accessibility: it removes the element from the accessibility tree.

Browser support and 2026 usage

@starting-style and transition-behavior: allow-discrete are both widely supported in 2026: Chrome 117+, Safari 17.5+, Firefox 129+. This covers approximately 90% of global users. Older browsers (old Android WebView, old iOS Safari) fall back to instant show/hide — the element is still usable, it just doesn't animate.

Progressively enhance with entry/exit transitions. The functional baseline (show/hide without animation) should still work for users without support.