@starting-style

Define starting values for CSS transitions when elements first appear — enabling fade-ins and slide-ins for popovers, newly inserted DOM elements, and display:none toggles. Pure CSS, zero animation JS.

1. Popover Fade-in — [popover]:popover-open

Click any button to show a popover that fades in with @starting-style { opacity: 0; transform: scale(0.9) }. Click again or click outside to dismiss — it fades out too. Zero JS animation code.

Popover content

This faded in with @starting-style. No JS animation.

Second popover

Same CSS transition rules apply to all [popover] elements.

2. New DOM Element Entry — @starting-style on insert

Click the button to dynamically insert a new list item into the DOM. Each item slides in from the left using @starting-style — the browser transitions from the starting values on first style update. No JS animation, no requestAnimationFrame.

    3. From display:nonetransition-behavior: allow-discrete

    Toggle an element between display: none and display: block with a smooth fade + slide. Previously impossible without JS animation — now one CSS rule.

    Visible!

    This element transitioned from display: none to display: block using @starting-style and transition-behavior: allow-discrete.

    4. Code Comparison — Bootstrap JS vs @starting-style

    Bootstrap JS: .fade + .show
    /* _transitions.scss */
    .fade {
      transition: opacity .15s linear;
      &:not(.show) { opacity: 0; }
    }
    
    /* js/src/util/index.js */
    const reflow = element => {
      element.offsetHeight;
    };
    
    const executeAfterTransition =
      (callback, transitionElement) => {
      const duration =
        getTransitionDurationFromElement(
          transitionElement
        ) + 5;
      let called = false;
      const handler = ({ target }) => {
        if (target !== transitionElement)
          return;
        called = true;
        transitionElement
          .removeEventListener(
            'transitionend', handler
          );
        callback();
      };
      transitionElement
        .addEventListener(
          'transitionend', handler
        );
      setTimeout(() => {
        if (!called)
          transitionElement
            .dispatchEvent(
              new Event('transitionend')
            );
      }, duration);
    };
    
    /* js/src/tooltip.js — show() */
    tip.classList.add('show');
    this._queueCallback(
      complete, this.tip,
      this._isAnimated()
    );
    
    /* js/src/tooltip.js — hide() */
    tip.classList.remove('show');
    this._queueCallback(
      complete, this.tip,
      this._isAnimated()
    );
    Native: @starting-style (zero JS)
    /* CSS only — no JS files needed */
    [popover] {
      opacity: 0;
      transform: scale(0.9);
      transition:
        opacity .3s ease,
        transform .3s ease,
        display .3s allow-discrete,
        overlay .3s allow-discrete;
    }
    
    [popover]:popover-open {
      opacity: 1;
      transform: scale(1);
    }
    
    @starting-style {
      [popover]:popover-open {
        opacity: 0;
        transform: scale(0.9);
      }
    }
    
    /* No reflow hack needed.
       No transitionend listener.
       No setTimeout fallback.
       No classList.add/remove.
       The browser handles entry
       and exit transitions
       from these CSS rules alone. */
    Implementation: @starting-style (key code)
    <!-- HTML: button + popover -->
    <button popovertarget="my-pop">Show</button>
    <div id="my-pop" popover>Content</div>
    
    <style>
      /* Closed state (also exit transition target) */
      [popover] {
        opacity: 0;
        transform: scale(0.9);
        transition: opacity .3s, transform .3s,
          display .3s allow-discrete,
          overlay .3s allow-discrete;
      }
    
      /* Open state */
      [popover]:popover-open {
        opacity: 1;
        transform: scale(1);
      }
    
      /* Entry transition: start from these values */
      @starting-style {
        [popover]:popover-open {
          opacity: 0;
          transform: scale(0.9);
        }
      }
    </style>
    
    <!-- DOM element entry animation -->
    .entry-item {
      opacity: 1; transform: translateX(0);
      transition: opacity .3s, transform .3s;
    }
    @starting-style {
      .entry-item {
        opacity: 0; transform: translateX(-20px);
      }
    }
    
    <!-- display:none toggle -->
    .box { display: none; opacity: 0;
      transition: opacity .3s, display .3s allow-discrete; }
    .box.visible { display: block; opacity: 1; }
    @starting-style { .box.visible { opacity: 0; } }