animation-trigger | CSS-Tricks
The CSS animation-trigger property delays the start of a CSS animation until a specific trigger occurs. More specifically, it listens for a named trigger and controls how the animation plays or pauses in response.
.element {
animation: fade-in 0.35s ease-in-out both;
animation-trigger: –trigger play-forwards play-backwards;
}
CodePen Embed Fallback
This behavior has traditionally been JavaScript territory, typically with the Intersection Observer API.
Scroll-triggered animations should not be confused with scroll-driven animations. Although both rely on scroll or view timelines, they are fundamentally different concepts. We’ll get to those differences in a minute.
The animation-trigger property is defined in the Animation Triggers specification
Syntax
animation-trigger: none | [];
- Initial value: none
- Applies to: all elements
- Inherited: no
- Computed value: as specified
- Animation type: not animatable
Values
The property accepts none, or a comma-separated list of triggers and their corresponding actions:
- none: The default state. The animation behaves normally and is not a triggered animation.
- : The dashed ident (e.g., –fade-in-trigger) of the trigger you want to listen to. This must match a name defined by the timeline-trigger or event-trigger properties.
- : One or more keywords dictating what the animation should do when the trigger activates (and optionally, when it deactivates). This can be further divided into:
- : Dictates what happens when the trigger state becomes “active”.
- (optional): Dictates what happens when the trigger state returns to “inactive”. By default, this is none.
The “trigger” in animation-trigger can refer to either timeline-based triggers, such as scroll or view progress timelines, or event-based triggers like DOM events (e.g., a click). In this entry, we’ll mainly focus on timeline triggers. If you’re curious about event-based triggers, you can check out the official spec.
By default, trigger names have a global scope. If multiple elements define the same trigger name, the element that comes later in the cascade gets chosen. You can restrict the scope of a trigger to a specific DOM subtree with the trigger-scope property.
Animation Actions
- play-forwards: Sets the playback rate to positive and plays the animation.
- play-backwards: Sets the playback rate to negative and plays the animation in reverse.
- play: Simply plays the animation at its current rate.
- play-once: Plays the animation only from its initial or paused state. It ignores the trigger if the animation has already finished playing.
- pause: Freezes the animation in place.
- reset: Instantly sets the animation progress back to 0 and pauses it.
- replay: Sets the progress back to 0 and immediately plays it.
- none: Does nothing.
Some actions aren’t or only, meaning it’s possible to play-backwards when entering and play-forwards when exiting.
Timeline Triggers
To actually use animation-trigger, you’ll typically need to set up a timeline trigger first. That controls when an animation starts based on where an element is within a timeline (like scroll or viewport position). More specifically, it activates when the element enters a defined activation range within that timeline.
To set up your timeline trigger, you’ll first define a custom (like –fade-in) to link it to your animation-trigger , followed by a timeline like a view() or scroll() function.
timeline-trigger-name: –fade-in;
timeline-trigger-source: view();
Then, you specify the (such as contain) to dictate exactly when the trigger turns “on” inside the viewport.
timeline-trigger-activation-range: contain;
You can also provide an optional to define the outer boundary where the trigger stays active before turning “off”, though if you leave it out, the browser just defaults to your activation range.
timeline-trigger-active-range: cover;
To get a better feel for how this works, check out the timeline ranges visualizer from the Chrome team.
CodePen Embed Fallback
If you use different ranges, the active range must include the activation range; otherwise, the trigger can’t turn “on.”
While you can use all the individual longhand properties:
- timeline-trigger-name
- timeline-trigger-source
- timeline-trigger-activation-range
- timeline-trigger-active-range
…you’ll almost always want to use the shorthand instead:
timeline-trigger: none | [ / ];
Unlike many CSS shorthands (like background or border), the order of values matters here, so you can’t rearrange them freely.
It’s worth noting that triggers and animations don’t have to be on the same element. You can define the timeline-trigger on a parent and apply the animation-trigger to multiple child elements. When the parent enters view, all children can animate together.
Using triggers
Let’s create a simple text reveal animation. We’ll use an element as the trigger point. Once you scroll past it, the animation plays and the text fades in.
First, define a timeline trigger on the trigger element:
.trigger {
timeline-trigger: –trigger scroll() contain / cover;
}
This sets up a trigger named –trigger that activates based on scroll position. The range contain / cover means the animation is triggered when the element is fully visible in the scrollport (contain) and continues to run as long as any part of it remains visible in the scrollport (cover).
Next, we’ll apply the animation-trigger to the text you want to animate, along with the animation itself:
.text {
animation-trigger: –trigger play;
animation: fade 0.6s ease-out;
}
The cool part is that you can mix and match different animation-action values to get very different behaviors using the same trigger. Here’s a quick demo showing the same setup with different animation actions:
CodePen Embed Fallback
With scroll-driven animations, the animation’s progress is directly tied to scroll position. As you scroll, the animation scrubs forward or backward in sync with the timeline. There’s no concept of a “start” or “fire” moment.
In contrast, scroll-triggered animations are state-based rather than continuous. A trigger has a binary state and when a condition is met—such as an element entering a defined range—the trigger fires an associated action (like play, pause, or reset). Once triggered, the animation behaves like your regular CSS animations with no link to the scroll progress.
Here’s a pretty cool demo to see the differences between the two, courtesy of utlitybend:
CodePen Embed Fallback
Specification
The animation-trigger property is defined in the Animation Triggers specification, which is currently in Editor’s Drafts. That means the information can change between now and what it becomes an official Candidate Recommendation.
Browser Support
Just Chrome 145+ at the time of this writing.





