
Company News
Socket Named Top Sales Organization by RepVue
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.
animate-presence
Advanced tools
Unlike most animation libraries, there's no new API to learn—just use CSS or the Web Animations API.
Here's a basic example using CSS. See Declarative Animations.
<animate-presence>
<div class="item">Item A</div>
<div class="item">Item B</div>
<div class="item">Item C</div>
</animate-presence>
<style>
.item[data-enter] {
animation: fade 1s ease-in;
}
.item[data-exit] {
animation: fade 1s ease-out reverse;
animation-fill-mode: both;
}
@keyframes fade {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
Here's a basic example using WAAPI. See Imperative Animations.
import { createPresenceHandler } from 'animate-presence';
const List = () => {
const fade = new Animation({
opacity: [0, 1],
});
const handleEnter = createPresenceHandler(async el => {
return el.animate(fade, {
duration: 1000,
easing: 'ease-in',
fill: 'both',
});
});
const handleExit = createPresenceHandler(async el => {
return el.animate(fade, {
duration: 1000,
easing: 'ease-out',
direction: 'reverse',
fill: 'both',
});
});
return (
<animate-presence
onAnimatePresenceEnter={handleEnter}
onAnimatePresenceExit={handleExit}
>
<div class="item">Item A</div>
<div class="item">Item B</div>
<div class="item">Item C</div>
</animate-presence>
);
};
As child elements are added or removed, data-enter and data-exit attributes are automatically applied.
Using CSS, you can use these attributes as hooks to apply an animation or transition.
Once the animation finishes, <animate-presence> automatically cleans itself up, removing the attribute and any listeners.
| Attribute | Description |
|---|---|
data-enter | Applied immediately when a node enters. Trigger your entrance animation here. |
data-exit | Applied when a node will be removed. Trigger your exit animations here. The node will be removed when the animation completes. |
Note Animate Presence overrides the default
animation-fill-modeof animating children toboth(rather thannone). This provides more reasonable default behavior in most situations, so the rule is injected with a specificity (021) high enough to override theanimationshorthand for most rules (like.item[data-enter]).
If you experience visual flickering at the start or end of an animation, you may be using a selector with a higher specificity than
021. Theanimationshorthand resets theanimation-fill-modetonone, so you likely need to manually specifyboth.
When multiple elements enter/exit, Animate Presence automatically sets a custom property, --i, on each child.
This makes staggered animations simple with a small calc function.
[data-enter] {
animation-delay: calc(var(--i, 0) * 50ms);
}
Using the Web Animations API to imperatively animate entrances/exits is the best way to coordinate complex, multi-step animations.
It's also possible to use your favorite animation library, like Popmotion or Anime.js!
| Event | Description |
|---|---|
animatePresenceEnter | Dispatched immediately when a node enters. Handle your entrance animation here. |
animatePresenceExit | Dispatched when a node will be removed. Handle your exit animations here. The node will be removed when the handler returns. |
As you might expect, these events are dispatched to the children of animate-presence as they enter or exit.
These events bubble, so listeners can be attached directly to an animate-presence element via addEventListener or onAnimatePresenceEnter/Exit handlers.
createPresenceHandlerUse the included createPresenceHandler utility to create async callbacks for these events.
import { createPresenceHandler } from 'animate-presence';
const onExit = createPresenceHandler(async (el, { i }) => {
// Note that `element.animate()` doesn't return a Promise, it returns an `Animation`.
// In order to await the `Animation`, you want `Animation.finished`.
await el.animate(
[{ transform: 'translateY(0)' }, { transform: 'translateY(50%)' }],
{
duration: 300,
delay: i * 50,
easing: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
fill: 'both', // Be sure to set this!
}
).finished;
// `createPresenceHandler` can optionally `return el.animate()` instead of using `await el.animate().finished`
await el.animate(
[
{ opacity: 1, transform: `translate(0, 50%)` },
{ opacity: 0, transform: `translate(16px, 50%)` },
],
{
duration: 300,
easing: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
fill: 'both', // Be sure to set this!
}
).finished;
});
const ap = document.querySelector('animate-presence');
ap.addEventListener('animatePresenceExit', onExit);
Animate Presence uses a tree-based approach, meaning that nested animate-presence elements are aware of their parents and children.
Enter animations are applied top-down, meaning the top-level parent enters, which triggers the next child entrance, and so on.
Exit animations are applied bottom-up, meaning the deepest child exits, which triggers the next parent exit, and so on.
Animate Presence relies on
querySelectorAllto construct the internal tree, so it does not (yet) work with Shadow Roots. This is an area I'm actively looking into.
For Stencil apps using @stencil/router, Animate Presence has an <animated-route-switch> component which allows you to smoothly animate between routes.
Swap your <stencil-route-switch> component with <animated-route-switch>
Due to a current bug in @stencil/router, you will need to use injectHistory to pass location down to animated-route-switch.
Hopefully this will be fixed soon, but there's a simple work around for now.
import { Component, h, Prop } from '@stencil/core';
import { injectHistory, LocationSegments } from '@stencil/router';
@Component({
tag: 'app-root',
styleUrl: 'app-root.css',
scoped: true,
})
export class AppRoot {
@Prop() location: LocationSegments;
render() {
return (
<div>
<header>
<h1>Stencil App Starter</h1>
</header>
<main>
<stencil-router>
{/* Pass `location` to animated-route-switch */}
<animated-route-switch location={this.location}>
{/* <animate-presence> should exist somewhere within these components */}
<stencil-route url="/" exact={true} component="app-home" />
<stencil-route url="/profile/:name" component="app-profile" />
</animated-route-switch>
</stencil-router>
</main>
</div>
);
}
}
injectHistory(AppRoot);
[data-enter] and [data-exit] as usual.As noted above, if your Stencil components rely on
shadowencapsulation, Animate Presence won't work as expected (yet). I'm working on it. For now, you can either usescopedencapsulation or stay tuned for updates!
FAQs
Effortless element entrance/exit animations
The npm package animate-presence receives a total of 90 weekly downloads. As such, animate-presence popularity was classified as not popular.
We found that animate-presence demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket won two 2026 Reppy Awards from RepVue, ranking in the top 5% of all sales orgs. AE Alexandra Lister shares what it's like to grow a sales career here.

Security News
NIST will stop enriching most CVEs under a new risk-based model, narrowing the NVD's scope as vulnerability submissions continue to surge.

Company News
/Security News
Socket is an initial recipient of OpenAI's Cybersecurity Grant Program, which commits $10M in API credits to defenders securing open source software.