The CSS animation-trigger Property
The CSS animation-trigger property controls when a CSS animation starts by tying it to a named trigger. Instead of an animation running immediately on page load, it waits for a specific condition to be met—like an element scrolling into view. When that condition activates, the animation plays, pauses, or resets according to the actions you define.
This is behavior that has traditionally required JavaScript, usually through the Intersection Observer API. The property is defined in the Animation Triggers specification, which is currently an Editor's Draft and subject to change before it reaches Candidate Recommendation status.
Experimental: Check browser support before using this in production. At the time of writing, only Chrome 145+ supports it.
Property Syntax and Values
animation-trigger: none | <trigger-name> <enter-action> [<exit-action>];
- Initial value:
none - Applies to: all elements
- Inherited: no
- Computed value: as specified
- Animation type: not animatable
The property accepts none, or a comma-separated list of triggers and actions:
none: The animation is not triggered and behaves normally.<trigger-name>: A dashed ident (e.g.,--fade-in-trigger) matching a trigger defined by thetimeline-triggerorevent-triggerproperties.<animation-action>: Keywords that dictate the animation's behavior when the trigger activates. This splits into an<enter-action>(what happens when the trigger becomes "active") and an optional<exit-action>(what happens when it returns to "inactive"; defaults tonone).
Available Animation Actions
play-forwards: Sets a positive playback rate and plays.play-backwards: Sets a negative playback rate and plays in reverse.play: Plays at the current rate.play-once: Plays only from the initial or paused state; ignores the trigger once the animation has finished.pause: Freezes the animation.reset: Sets progress to0and pauses.replay: Sets progress to0and immediately plays.none: Does nothing.
Actions are not restricted to a single direction. For instance, you can have play-backwards on enter and play-forwards on exit.
The Distinction: Scroll-Triggered vs. Scroll-Driven
The animation-trigger property enables scroll-triggered animations, which should not be confused with scroll-driven animations. In a scroll-driven animation, progress is directly tied to scroll position—scrubbing forward and backward as the user scrolls. There is no "fire" moment.
Scroll-triggered animations are state-based. A trigger has a binary on/off condition, and when it fires (e.g., an element enters a defined range), it executes an associated action like play, pause, or reset. Once triggered, the animation runs independently of scroll position, just like a standard CSS animation.
Setting Up a Timeline Trigger
To use animation-trigger with scroll or viewport position, you first define a custom trigger with the timeline-trigger shorthand (or its longhands). The syntax requires a <trigger-name>, a <source> timeline (such as view() or scroll()), and an activation range:
timeline-trigger-name: --fade-in;
timeline-trigger-source: view();
The <activation-range> (e.g., contain) dictates when the trigger turns "on" inside the viewport. Optionally, an <active-range> defines the outer boundary where the trigger stays active before turning off; if omitted, it defaults to the activation range.
timeline-trigger-activation-range: contain;
timeline-trigger-active-range: cover;
The active range must include the activation range, or the trigger can never turn on. Note that trigger names have a global scope by default: if multiple elements share the same trigger name, the later one in the cascade wins. You can scope a trigger to a specific DOM subtree with the trigger-scope property.
While the individual longhand properties (timeline-trigger-name, timeline-trigger-source, timeline-trigger-activation-range, timeline-trigger-active-range) exist, the shorthand is nearly always preferable:
timeline-trigger: none | <trigger-name> <source> <activation-range> [ / <active-range>];
Unlike many CSS shorthands (e.g., background or border), the order of values matters—you cannot rearrange them.
Triggers and animations do not need to live on the same element. You can define a timeline-trigger on a parent element and apply animation-trigger to multiple children, so when the parent enters view, all children animate together.
Example: Scroll-Triggered Text Reveal
A simple text reveal illustrates how this works. First, define a timeline trigger on a "trigger point" element, so the text fades in when the user scrolls past it:
.trigger {
timeline-trigger: --trigger scroll() contain / cover;
}
Here, the trigger named --trigger activates based on scroll position. The range contain / cover means it fires when the element is fully visible within the scrollport (contain) and stays active as long as any part of it remains visible (cover).
Then apply the trigger and animation to the target text:
.text {
animation-trigger: --trigger play;
animation: fade 0.6s ease-out;
}
By combining the same trigger with different animation-action values, you can create very different behaviors—from a one-time fade-in to a toggle that plays forward and backward as the element enters and leaves the viewport.



