Skip to content

Custom animation

Animate the expander part with the --opening and --closing states. Keep grid-template-rows: 1fr during closing when the keyframes do not animate rows.

Version 3.2

Workspace snapshots

Capture a workspace state, compare later changes, and restore the version you need.

Automatic history One-click restore

Code

The following example assumes that you have imported the component and set up the theme.

Custom flyout animation
<div class="custom-animation-example">
<sv-flyout class="update-flyout" position-order="bottom center center, top center center">
<button type="button" class="update-trigger" slot="trigger">
<span aria-hidden="true"></span>
See what’s new
</button>
<article class="update-card">
12 collapsed lines
<span class="update-version">Version 3.2</span>
<h2>Workspace snapshots</h2>
<p>Capture a workspace state, compare later changes, and restore the version you need.</p>
<div class="update-details">
<span>Automatic history</span>
<span>One-click restore</span>
</div>
</article>
</sv-flyout>
</div>
<style>
19 collapsed lines
.custom-animation-example {
display: grid;
place-items: center;
min-block-size: 320px;
.update-trigger {
display: flex;
gap: 0.5em;
align-items: center;
padding: 0.65em 1em;
font: inherit;
font-weight: 650;
color: var(--sv-gray-1);
cursor: pointer;
background: var(--sv-gray-6);
border: solid var(--sv-bdr-sz-sm) var(--sv-gray-5);
border-radius: var(--sv-bdr-rad-md);
}
.update-flyout {
/* Override default durations. */
--sv-open-dur: 400ms;
--sv-close-dur: 300ms;
/* --ease-*() syntax is replaced with linear() at build time you can use your own easing functions. */
--update-enter-ease: --ease-out-wobble(2.4);
--update-exit-ease: --ease-in-back(6);
&::part(content) {
inline-size: min(22rem, calc(100vw - 2rem));
min-block-size: 200px;
padding: 0;
}
&:state(--opening)::part(expander) {
/* Apply the custom animation for the opening phase. */
animation: update-card-enter var(--sv-open-dur) var(--update-enter-ease);
}
&:state(--closing)::part(expander) {
/*
Keep the expander laid out while the visual exit runs.
The component hides the popover after this animation ends.
*/
grid-template-rows: 1fr;
/* Apply the custom animation for the closing phase. */
animation: update-card-exit var(--sv-close-dur) var(--update-exit-ease);
}
/* Custom animations must explicitly honor the user’s motion preference. */
@media (prefers-reduced-motion: reduce) {
--sv-open-dur: 0s;
--sv-close-dur: 0s;
}
}
40 collapsed lines
.update-card {
padding: 1.25em;
color: var(--sv-gray-1);
background:
radial-gradient(circle at top right, color-mix(in srgb, var(--sv-accent) 22%, transparent), transparent 45%),
var(--sv-gray-7);
.update-version {
font-size: 0.75rem;
font-weight: 750;
color: var(--sv-accent);
text-transform: uppercase;
letter-spacing: 0.08em;
}
h2 {
margin: 0.35em 0 0;
font-size: 1.25rem;
}
p {
margin: 0.65em 0 1em;
line-height: 1.5;
color: var(--sv-gray-2);
}
.update-details {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
span {
padding: 0.35em 0.6em;
font-size: 0.8rem;
background: var(--sv-gray-6);
border-radius: 100vmax;
}
}
}
}
@keyframes update-card-enter {
from {
opacity: 0;
transform: perspective(650px) translateY(1.25rem) rotateX(-24deg) rotateZ(-2deg);
scale: 0.78;
}
to {
opacity: 1;
transform: perspective(650px) translateY(0) rotateX(0) rotateZ(0);
scale: 1;
}
}
@keyframes update-card-exit {
from {
opacity: 1;
transform: perspective(650px) translateY(0) rotateX(0) rotateZ(0);
scale: 1;
}
to {
opacity: 0;
transform: perspective(650px) translateY(-1.25rem) rotateX(16deg) rotateZ(-1deg);
scale: 0.9;
}
}
</style>