v151 · css · web animations · behavior change
No Auto-Rewind for AnimationTrigger Play Methods
The play, play-forwards, and play-backwards actions of an AnimationTrigger no longer auto-rewind: when the trigger fires and its animation has already run to completion, the animation is not restarted. Re-running a finished animation from the start now requires an explicit rewinding action such as replay.
AnimationTrigger is stable at trunk; on versions where it is not enabled, it sits behind chrome://flags/#enable-experimental-web-platform-features. The governing CSSWG resolution (#13643, 2026-06-03) is newer than the animation-triggers-1 Editor's Draft — the draft still shows the old finished-state semantics — and the same resolution also adds play-always and removes play-once, which Chrome 151 does not implement (Chromium's IDL still lists play-once). Do not assume cross-browser behavior; test before relying on this in production.
at a glance
| The change | The trigger actions play, play-forwards, and play-backwards invoke Web Animations' “play an animation” procedure with its auto-rewind flag effectively false: a finished animation stays finished when the trigger refires. Script-called animation.play() is unchanged — it still passes auto-rewind = true and still restarts a finished animation |
|---|---|
| Chrome status | Enabled by default in Chrome 151 — desktop, Android, WebView (the milestone listing is authoritative, verified 2026-07-28; feature record, accurate_as_of 2026-07-13) |
| CSSWG decision | w3c/csswg-drafts #13643 — RESOLVED 2026-06-03: “play, play-f, play-b do not auto-rewind at the end; we add play-always which does; we remove play-once since that's now play behavior”. Scope boundary: this Chrome 151 change implements only the no-auto-rewind part; play-always and the play-once removal are not part of it (Chromium's animation_trigger.idl at trunk still lists play-once and has no play-always) |
| Spec state | The animation-triggers-1 editor's draft action table (fetched 2026-07-28) still shows the OLD finished-column semantics (play() on finished); the spec edit implementing the resolution has not landed. This page documents the shipping Chrome 151 behavior, which follows the resolution |
| Runtime flag | AnimationTrigger — status stable at trunk, implied_by EventTrigger and TimelineTrigger (runtime_enabled_features.json5) |
| WPT | animation-trigger-play-forwards-backwards.tentative.html and animation-trigger-play.tentative.html assert the new semantics (“does not restart it once finished”, “does not restart a finished animation”) |
| ChromeStatus | 5071640598806528 — No Auto-Rewind for AnimationTrigger Play Methods |
why the change
play-forwards and play-backwards exist so that “opposite” events can drive opposite visual effects — for example pointerdown swelling a button and pointerup shrinking it back. Under the old table, refiring the trigger when the animation had finished invoked play(), which (per Web Animations' auto-rewind) seeks back and restarts the animation — so a second pointerdown would replay the swell from the start instead of leaving the button swelled. The CSSWG discussion concluded developers do not expect a finished animation to restart, and resolved that the play actions do not auto-rewind; the resolution was applied to play as well “for consistency” (feature motivation).
syntax
The actions are values of <animation-action>, used in the animation-trigger shorthand (and, in Chromium's JS API, as AnimationTriggerBehavior enum values):
/* animation-triggers-1 grammar */
animation-trigger: [ none | [ <dashed-ident> <animation-action>+ ]+ ]#
event-trigger-source: [ none | <event-trigger-event>+ [ / <event-trigger-event>+ ]? ]#
<animation-action> = none | play | play-once | play-forwards | play-backwards | pause | reset | replay
// Chromium IDL — animation_trigger.idl (trunk)
// https://drafts.csswg.org/web-animations-2/#the-animationtrigger-interface
// https://github.com/w3c/csswg-drafts/issues/12611#issue-3326243729
enum AnimationTriggerBehavior {
"play",
"pause",
"reset",
"play-once",
"play-forwards",
"play-backwards",
"replay",
"none",
};
[
Exposed=Window,
RuntimeEnabled=AnimationTrigger
] interface AnimationTrigger {
[RaisesException] void addAnimation(Animation animation,
AnimationTriggerBehavior activateBehavior,
optional AnimationTriggerBehavior deactivateBehavior = "none");
void removeAnimation(Animation animation);
sequence<Animation> getAnimations();
};
Concrete trigger subclasses (timeline_trigger.idl, event_trigger.idl): TimelineTrigger is constructed with a sequence of options and exposes a ranges list; EventTrigger takes EventTriggerOptions and exposes eventType/eventTarget.
before and after
The animation-triggers-1 editor's draft (fetched 2026-07-28) still publishes the OLD table. The “after” column is the CSSWG resolution as implemented in Chrome 151 and asserted by WPT:
| Keyword | Extra effect | Trigger fires, animation is finished — BEFORE (current draft) | AFTER (Chrome 151 / CSSWG resolution) |
|---|---|---|---|
play | — | play() — restarts the finished animation (auto-rewind) | no effect — the animation stays finished |
play-forwards | set playback rate to positive | play() — restarts | no restart — but see the direction nuance below |
play-backwards | set playback rate to negative | play() — restarts | no restart — mirror of play-forwards |
replay | set progress to 0 | play() | unchanged — this is the explicit-rewind action |
pause / reset / play-once / none | unchanged by this feature (play-once already did nothing when finished; note the CSSWG separately resolved to remove play-once and add play-always — not part of Chrome 151) | ||
The direction nuance (from WPT, not prose): play-forwards sets the playback rate positive before the state-specific step. If the animation finished playing backwards (playback rate −1, current time 0), a play-forwards trigger flips the rate to +1 and the animation plays forwards from time 0 — that is a direction reversal, not an auto-rewind, and it still happens. What no longer happens is replaying an animation that finished in the same direction: rate already +1, current time at the end → refiring does nothing. play-backwards mirrors this exactly.
- animation-triggers-1 ED (fetched 2026-07-28): action table still shows the pre-resolution finished-column semantics.
- web-animations-2 ED §4.16–4.18 (fetched 2026-07-28): its
AnimationTriggerIDL is the OLD trigger model (behaviorattribute withonce/repeat/alternate/state, range attributes). Chromium does not implement that shape; it implements the #12611 action-set model (addAnimation(animation, activateBehavior, deactivateBehavior)). - Chromium 151: ships the #12611 model + the #13643 no-auto-rewind behavior, without
play-alwaysand still withplay-once.
action: play
Plays the associated animation when the trigger activates. After Chrome 151, a finished animation is left finished; a paused animation resumes from its current time (WPT: “play behavior resumes a paused animation”, “play behavior does not restart a finished animation”).
- Syntax
<animation-action> = play— used asanimation-trigger: --name play, ortrigger.addAnimation(anim, "play")(animation-triggers-1 <animation-action>).- Inputs
- No parameters. The action applies to the animation(s) associated with the trigger name (CSS) or passed to
addAnimation(JS); the number of actions a trigger accepts depends on its type (event triggers one, optionally a second for stateful; timeline triggers one, optionally a second — animation-triggers-1). - Outputs
- Observable play states per WPT: idle/initial → running; paused → resumes (current time continues from where it paused); playing → no effect; finished → stays finished (no restart) — WPT animation-trigger-play.tentative.html.
- Errors
- As a CSS value, an unknown keyword makes the declaration invalid at computed-value time (standard CSS error handling). No exception path exists for the CSS form. For the JS enum form, see the AnimationTriggerBehavior section.
- Context
- Applies to animations associated with a trigger via
animation-trigger(CSS) orAnimationTrigger.addAnimation(JS, Window-exposed,RuntimeEnabled=AnimationTrigger, stable at trunk — runtime_enabled_features.json5). No permission or secure-context requirement is recorded in the sources consulted. - Lifecycle
- Fires on trigger activation (stateless: every activation; stateful: only on the inactive→active transition). If the same trigger name is specified multiple times, all but the last have no effect; simultaneous triggers take effect in specified order (animation-triggers-1). The no-rewind state test is the animation's play state at invocation time: finished (same-direction) → nothing happens.
- Examples
.target { animation: expand linear 10s both; /* play once when --t activates; if it already finished, stay finished */ animation-trigger: --t play; } /* Explicit rewind instead: animation-trigger: --t replay; */- Compatibility
- Behavior change: Chrome 151+ (desktop, Android, WebView — ChromeStatus record; no BCD entry yet — interim). The underlying action exists since Chrome 146 (BCD). Firefox/Safari: no AnimationTrigger support and no signal.
Surface Chrome Firefox Safari playaction (no-rewind semantics)151+ n/a — API unsupported n/a — API unsupported playaction (original semantics)146–150 n/a n/a Behavior-change row is interim from the ChromeStatus record (no BCD entry yet); the keyword itself dates to Chrome 146 per BCD. Firefox/Safari: no AnimationTrigger support and no signal.
- Security / privacy
- animation-triggers-1 records no known privacy or security impacts for the triggers feature (§3, §4); this behavior change alters no trust boundary.
action: play-forwards
Sets the animation's playback rate to positive, then plays. The motivating use case is paired opposite effects (e.g. pointerdown/pointerup). After Chrome 151 it does not restart an animation that already finished in the forwards direction — but it still reverses an animation that finished playing backwards.
- Syntax
<animation-action> = play-forwards—animation-trigger: --press play-forwards, ortrigger.addAnimation(anim, "play-forwards", "play-backwards")(animation-triggers-1 value definition).- Inputs
- No parameters. The extra effect (set playback rate positive) happens regardless of the animation's current state, before the state-specific action (action table caption).
- Outputs
- Per WPT: finished-at-start with playback rate −1 → rate flips to +1, animation runs forwards (direction reversal, still supported); finished-at-end with rate +1 → exit + re-enter leaves it finished at the end, rate +1 (WPT play-forwards-backwards test: “plays a finished animation if reversing direction, and does not restart it once finished”).
- Errors
- Unknown CSS keywords invalidate the declaration; no runtime exception path. JS enum form: see AnimationTriggerBehavior.
- Context
- Same association paths as
play. Works with both timeline triggers (e.g.timeline-trigger: --t view() 150px 200px) and event triggers (animation-triggers-1 §2). - Lifecycle
- Rate is set positive first, then the finished-state test applies: if the animation's play state is finished at the positive-rate end, nothing further happens; if it finished at the opposite boundary (rate negative, time 0), the rate flip makes it runnable and it plays forwards from 0 (WPT).
- Examples
@keyframes press-animation { to { transform: translate(-5px, 5px); } } button { animation: press-animation forwards; event-trigger: --press pointerdown / pointerup; /* stateful: enter/exit */ animation-trigger: --press play-forwards play-backwards; } /* Repeated pointerdowns no longer restart the swell; pointerup plays it back once. */- Compatibility
- Behavior change: Chrome 151+ (interim — no BCD entry yet); keyword itself since Chrome 146 (BCD). Firefox/Safari: unsupported, no signal.
Surface Chrome Firefox Safari play-forwards(no-rewind semantics)151+ n/a — API unsupported n/a — API unsupported play-forwards(original semantics)146–150 n/a n/a Interim data as above (ChromeStatus record, BCD).
- Security / privacy
- No known privacy or security impact per animation-triggers-1 §3/§4.
action: play-backwards
The exact mirror of play-forwards: sets the playback rate negative, then plays. After Chrome 151 it does not restart an animation that already finished in the backwards direction, but still reverses one that finished playing forwards.
- Syntax
<animation-action> = play-backwards—animation-trigger: --depress play-backwards, or as the deactivate behaviortrigger.addAnimation(anim, "play-forwards", "play-backwards")(animation-triggers-1 value definition).- Inputs
- No parameters. Extra effect: set playback rate to negative, applied before the state-specific action, regardless of state (action table caption).
- Outputs
- Per WPT: finished-at-end with rate +1 → rate flips to −1, animation runs backwards from the end; finished-at-start with rate −1 → exit + re-enter leaves it finished at time 0, rate −1 (WPT play-forwards-backwards test).
- Errors
- Unknown CSS keywords invalidate the declaration; no runtime exception path. JS enum form: see AnimationTriggerBehavior.
- Context
- Same association paths as
play-forwards; commonly the second (deactivate/exit) action of a stateful trigger pair (animation-triggers-1 event triggers). - Lifecycle
- Mirror of
play-forwards: rate set negative first; finished at the negative-rate boundary (time 0) → no effect; finished at the positive boundary → plays backwards from the end (WPT). - Examples
.target { animation: slide 1s both; /* two stateless triggers: each plays one direction, never restarts */ animation-trigger: --c1 play-forwards, --c2 play-backwards; }- Compatibility
- Behavior change: Chrome 151+ (interim — no BCD entry yet); keyword itself since Chrome 146 (BCD). Firefox/Safari: unsupported, no signal.
Surface Chrome Firefox Safari play-backwards(no-rewind semantics)151+ n/a — API unsupported n/a — API unsupported play-backwards(original semantics)146–150 n/a n/a Interim data as above (ChromeStatus record, BCD).
- Security / privacy
- No known privacy or security impact per animation-triggers-1 §3/§4.
JS enum: AnimationTriggerBehavior play values
In Chromium's JS API, the same three actions are the "play", "play-forwards", and "play-backwards" values of AnimationTriggerBehavior, passed to AnimationTrigger.addAnimation(). The no-auto-rewind change applies identically through this path — the WPT play-forwards/backwards test drives the CSS path, while the addAnimation test exercises the same behaviors through the API.
- Syntax
[RaisesException] undefined addAnimation(Animation animation, AnimationTriggerBehavior activateBehavior, optional AnimationTriggerBehavior deactivateBehavior = "none")— animation_trigger.idl (verbatim in the Syntax section).- Inputs
activateBehavior: action on trigger activation (e.g."play-forwards");deactivateBehavior: action on deactivation, default"none"(e.g."play-backwards"). Valid values:play,pause,reset,play-once,play-forwards,play-backwards,replay,none(IDL).- Outputs
- Returns
undefined; the association is observable viatrigger.getAnimations()and the animation's subsequent play state. WPT: an animation added to an already-tripped (default) trigger with"play-forwards"plays to finish immediately;getAnimations()then contains it (WPT addAnimation test). - Errors
- Per standard WebIDL enum conversion, a string outside the eight enumerated values throws a
TypeError(WebIDL § enumeration conversion). The IDL marksaddAnimation[RaisesException]but the sources consulted (IDL file, WPT, linked specs) do not enumerate further exception conditions — none are documented here rather than invented. - Context
Exposed=Window,RuntimeEnabled=AnimationTrigger(stable at trunk, implied by theEventTrigger/TimelineTriggerflags — runtime_enabled_features.json5). Note the divergence: web-animations-2's ED shows a different (older) interface shape; Chromium implements the #12611 model.- Lifecycle
- Association persists until
removeAnimation(animation). Activation runs the activate behavior; deactivation (for stateful/timeline triggers) runs the deactivate behavior. The no-rewind test applies at each invocation exactly as on the CSS path. - Examples
const trigger = new TimelineTrigger([{ source: new ViewTimeline({ subject }), /* activation/active ranges per TimelineTriggerOptions */ }]); trigger.addAnimation(animation, "play-forwards", "play-backwards"); // Refiring after finish: stays finished (Chrome 151+). // trigger.addAnimation(animation, "replay"); // explicit rewind instead- Compatibility
- API root: Chrome 146+ per BCD api/AnimationTrigger.json (root entry only — BCD has no member sub-entries and MDN has no AnimationTrigger page, both checked 2026-07-28); behavior change Chrome 151+ (interim, no BCD entry). Firefox/Safari: unsupported, no signal.
Surface Chrome Edge Firefox Safari AnimationTrigger/AnimationTriggerBehavior(API root)146+ 146 (mirror) not supported not supported Play values with no-rewind semantics 151+ 151 (mirror) n/a — API unsupported n/a — API unsupported API row from BCD api/AnimationTrigger.json (root-only entry); behavior-change row interim from the ChromeStatus record.
- Security / privacy
- No known privacy or security impact per animation-triggers-1 §3/§4; the API exposes no new capability beyond animation playback control.
getting the old behavior back: replay
replay is the explicit-rewind action: its extra effect sets progress to 0, then plays — in every state including finished. Authors who relied on a play action restarting a finished animation migrate by renaming the action to replay.
- Syntax
<animation-action> = replay—animation-trigger: --t replay, ortrigger.addAnimation(anim, "replay")(animation-triggers-1 value definition).- Inputs
- No parameters. Extra effect: set progress to 0, applied regardless of state before playing (action table caption).
- Outputs
- The associated animation restarts from the beginning on every activation, including from finished — the pre-151 behavior of the play* actions (animation-triggers-1 action table; WPT replay test).
- Errors
- Unknown CSS keywords invalidate the declaration; JS enum form follows the same WebIDL TypeError contract.
- Context
- Same association paths and exposure as the play actions; available since Chrome 146 (BCD). The CSSWG resolution also promises a future
play-alwayskeyword for restarting plays — not implemented in Chrome 151 (#13643 resolution). - Lifecycle
- Progress is zeroed on every activation before the play step, so the animation always runs from the start; on deactivation of a stateful trigger the paired action runs instead.
- Examples
/* Before 151 this restarted on every activation: animation-trigger: --t play; */ /* After 151, use replay to keep restart-on-activation: */ .target { animation: expand linear 10s both; animation-trigger: --t replay; }- Compatibility
- Unchanged by this feature — Chrome 146+ (BCD). Firefox/Safari: unsupported, no signal.
Surface Chrome Firefox Safari replayaction146+ (unchanged by this feature) n/a — API unsupported n/a — API unsupported Per BCD; Firefox/Safari have no AnimationTrigger support and no signal.
- Security / privacy
- No known privacy or security impact per animation-triggers-1 §3/§4.
examples
play-vs-replay contrast (HEAD-checked 200, 2026-07-28). Source: chrome-platform-showcase — play-vs-replayMore interactive demos for this feature: scroll-reentry-lab and trigger-playground (both HEAD-checked 200, 2026-07-28).
browser compatibility
| Surface | Chrome | Edge | Firefox | Safari |
|---|---|---|---|---|
AnimationTrigger API (root) | 146 | 146 (mirror) | not supported | not supported |
animation-trigger CSS property | 146 | 146 (mirror) | not supported | not supported |
| No-auto-rewind for play actions (this feature) | 151 (desktop, Android, WebView) | 151 (mirror) | n/a — API unsupported | n/a — API unsupported |
The first two rows are from BCD (api/AnimationTrigger.json, css/properties/animation-trigger.json — both root-only entries, no member sub-entries). The behavior-change row has no BCD entry yet and is labelled interim from the ChromeStatus record and WPT assertions. Vendor positions on the record: Firefox and Safari both “No signal”; the record's compat-risk note states only Chromium ships AnimationTrigger.
Source: BCD api/AnimationTrigger.json; BCD css/properties/animation-trigger.json; ChromeStatus API record.specifications
| Specification | Relevance |
|---|---|
| CSS Animation Triggers Module Level 1 (editor's draft) | animation-trigger property and the <animation-action> table. ED table predates the resolution (see divergence note) |
| Web Animations Level 2 (editor's draft) — Animation Triggers | Trigger model; its IDL sections show the older interface shape Chromium has moved past (#12611) |
| Web Animations — “play an animation” procedure | Defines the auto-rewind flag this change bypasses; script play() passes true, unchanged |
see also
- Chrome Platform Status — No Auto-Rewind for AnimationTrigger Play Methods
- w3c/csswg-drafts #13643 — no-effect-if-finished discussion + 2026-06-03 CSSWG resolution
- w3c/csswg-drafts #12611 — set of actions for animation triggers (the model Chromium implements)
- WPT — scroll-animations/animation-trigger test suite
- Chromium tracking bug 519573765
- Chrome Platform Showcase — interactive demos for this feature