@starting-style / allow-discrete

Native Show/Hide Transitions

Transitioning something into existence used to mean never actually setting display: none, keeping it in the layout at opacity: 0 and pointer-events: none instead, plus a bit of JS to add display: none after the fact. @starting-style and transition-behavior: allow-discrete let display itself be the toggle, animated both ways.

Booster separation confirmed. No JS animation library involved.

The CSS

.toast {
  display: none;
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity 0.25s ease,
    transform 0.25s ease,
    display 0.25s allow-discrete;

  &[data-open='true'] {
    display: flex;
    opacity: 1;
    transform: translateY(0);

    /* the "before" frame for the entry transition */
    @starting-style {
      opacity: 0;
      transform: translateY(14px);
    }
  }
}

How it works

The toast never unmounts, a data-open attribute just flips its display between none and flex. Normally a transition can’t animate across a display change at all (there’s no box to animate from once it’s none), but listing display in the transition property with allow-discrete tells the browser to hold the old value for the outgoing frame, and the new one for the incoming frame, instead of snapping instantly.

@starting-style fills in the other half: the styles an element should transition from the moment it first becomes visible, since an element that was just display: none has no previous frame to animate from otherwise.

Chrome / Edge 117+, Safari 17.5+, Firefox 129+