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.
@starting-style at-rule defines the initial property values
an element transitions from when it first receives styles. Combined with
transition-behavior: allow-discrete, it enables smooth entry transitions for elements
entering the top layer or toggling display. This replaces Bootstrap’s
.fade + .show JS class toggling entirely.
[popover]:popover-openClick 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.
This faded in with @starting-style. No JS animation.
Same CSS transition rules apply to all [popover] elements.
@starting-style on insertClick 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.
display:none — transition-behavior: allow-discreteToggle an element between display: none and display: block with a smooth fade + slide. Previously impossible without JS animation — now one CSS rule.
This element transitioned from display: none to display: block using @starting-style and transition-behavior: allow-discrete.
@starting-style/* _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()
);
/* 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. */
<!-- 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; } }