@starting-style / allow-discrete

Native Show/Hide Transitions

Transitioning something into existence used to mean never really setting display: none: park it in the layout at opacity: 0 with pointer-events: none, then have JS add display: none afterwards. @starting-style and transition-behavior: allow-discrete let display be the toggle, animated both ways.

Booster separation confirmed. No JS animation library involved.

The CSS

Copied
.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 flips its display between none and flex. A transition normally can’t cross a display change at all, since there’s no box to animate from once it’s none, but listing display with allow-discrete tells the browser to hold the old value for the outgoing frame and the new one for the incoming frame.

@starting-style fills in the other half: the styles to transition from the first time an element becomes visible, since something that was just display: none has no previous frame to leave.

Widely supported

Chrome / Edge117+Safari17.5+Firefox129+
:has() · tic-tac-toe

turns, wins and reset, all CSS

@property · counter()
hover me
cos() · sin()

trigonometry, in CSS

:has() quantity query

switch guests on