Part 3 of 4 · CSS Shine Lab
The @starting-style rule
The values an element animates from the first time it renders. The missing half of transition that lets things animate as they appear, even from display: none.
- Innovation
Reach for @starting-style when you want an element to animate as it appears,
whether it is on the page from the start or something you have just added, like
an item a user creates and you drop into a list. Easing it in draws the eye to
what is new. This one is more recent than transitions and keyframes, but it is
supported in every major browser now. Firefox was last, in version 129, in
August 2024.
Implementation
On its own, transition can only animate between two rendered states, so it
cannot animate an element as it first appears: there is nothing to interpolate
from. @starting-style is that missing before. Put your resting styles on the
element as usual, add a transition, then list the initial values in a
@starting-style block. For elements that appear out of display: none, such
as popovers, tooltips, and dialogs, also transition display and overlay with
transition-behavior: allow-discrete so they switch at the right end of the
animation.
Fade and slide in on first render
The simplest form. The card eases from the values in its @starting-style block
to its resting styles the first time it renders. Reload the page, or use Replay,
to see it again.
View source
HTML
<div class="starting-card">I faded and slid in when the page loaded</div> CSS
.starting-card {
padding: 1rem 1.25rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface-elevated);
color: var(--color-text);
/* The resting ("after") state the element settles into. */
opacity: 1;
translate: 0 0;
transition:
opacity 0.6s ease,
translate 0.6s ease;
/* The "before" state, applied only for the element's very first render. The
transition above interpolates from here to the resting state on load. */
@starting-style {
opacity: 0;
translate: 0 1.5rem;
}
}
@media (prefers-reduced-motion: reduce) {
.starting-card {
transition: none;
}
} A popover that animates open and closed
@starting-style earns its place with things that pop into existence. This
native popover fades and scales in when opened, and allow-discrete on
display and overlay lets it animate back out before it is hidden, with no
JavaScript.
View source
HTML
<button popovertarget="starting-style-popover" class="starting-trigger">
Toggle popover
</button>
<div id="starting-style-popover" popover class="starting-popover">
I animate in and out with @starting-style + allow-discrete
</div> CSS
.starting-trigger {
padding: 0.7rem 1.2rem;
border: none;
border-radius: var(--radius-md);
background: var(--color-accent);
color: var(--color-on-accent);
font: inherit;
cursor: pointer;
}
/* The base rule doubles as the closed/exit state (transparent, scaled down);
:popover-open below is the shown state. `allow-discrete` lets the normally
un-animatable `display` and `overlay` take part, so the box can animate on
the way OUT before `display` finally flips to none. */
.starting-popover {
margin: auto;
padding: 1rem 1.25rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface-elevated);
color: var(--color-text);
opacity: 0;
scale: 0.95;
transition:
opacity 0.3s ease,
scale 0.3s ease,
display 0.3s ease allow-discrete,
overlay 0.3s ease allow-discrete;
}
.starting-popover:popover-open {
opacity: 1;
scale: 1;
}
/* The starting state for the OPEN popover — the "before" for the entry
animation. Needed because the popover appears from `display: none`, so there
is no prior rendered state for `transition` to animate from. */
@starting-style {
.starting-popover:popover-open {
opacity: 0;
scale: 0.95;
}
}
@media (prefers-reduced-motion: reduce) {
.starting-popover {
transition: none;
}
} A tooltip that fades in on hover
The same technique gives a CSS-only tooltip. It fades in every time it appears
on hover or focus, then fades out, with allow-discrete handling the switch in
and out of display: none.
View source
HTML
<span class="tooltip-host" tabindex="0">
Hover or focus me
<span class="tooltip-host__bubble" role="tooltip">A tooltip via @starting-style</span>
</span> CSS
.tooltip-host {
position: relative;
border-bottom: 1px dashed var(--color-border-strong);
cursor: help;
}
.tooltip-host__bubble {
position: absolute;
bottom: calc(100% + 0.5rem);
left: 50%;
translate: -50% 0;
padding: 0.4rem 0.6rem;
border-radius: var(--radius-sm);
border: 1px solid var(--color-border);
background: var(--color-surface-elevated);
color: var(--color-text);
white-space: nowrap;
/* Hidden and transparent at rest. `allow-discrete` lets `display` flip at the
right edge of the animation, so the bubble can fade out before it hides. */
display: none;
opacity: 0;
transition:
opacity 0.2s ease,
display 0.2s ease allow-discrete;
}
.tooltip-host:hover .tooltip-host__bubble,
.tooltip-host:focus-visible .tooltip-host__bubble {
display: block;
opacity: 1;
/* Fade in from transparent each time the tooltip appears. */
@starting-style {
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.tooltip-host__bubble {
transition: none;
}
}