skip to content

In the CSS animation shorthand, how are two time values interpreted, and what happens to the longhands you leave out?

level: middleimportance: should knowfreq 46%

answer

  1. order is free except for the times
  2. first time, second time
  3. shorthands reset what you omit
  4. the name is matched last of all
  5. zero duration is the default

basics

~20 s

In the animation shorthand the first time value is animation-duration and the second is animation-delay, regardless of their size. Every longhand you omit is reset to its initial value, so a shorthand written later silently wipes an earlier animation-fill-mode or animation-play-state.

solid answer

~40 s

The `animation` shorthand accepts its components in almost any order, with one hard positional rule: the first `<time>` is `animation-duration` and a second `<time>` is `animation-delay`. So `animation: slide 2s 500ms` runs for two seconds after waiting half a second, and swapping them changes the meaning even though nothing else moved. The bigger trap is that, like every CSS shorthand, it resets the longhands you did not write to their initial values — `animation-fill-mode: none`, `animation-direction: normal`, `animation-iteration-count: 1`, `animation-play-state: running`. A rule that sets `animation-fill-mode: forwards` and then, later in the cascade, sets `animation: slide 2s` has no fill mode left. One more trap: the keyframes name is matched last, so naming a `@keyframes` rule `running`, `paused`, `infinite`, `reverse` or `none` makes the shorthand consume it as the matching keyword instead.

code

css · 13 lines
css
@keyframes slide {
  from { translate: -100% 0; }
  to   { translate: 0 0; }
}

/* 2s long, waits 500ms */
.a { animation: slide 2s 500ms ease-out both; }

/* 500ms long, waits 2s — same tokens, different meaning */
.b { animation: slide 500ms 2s ease-out both; }

/* Starts immediately, already 2s into a 4s loop */
.c { animation: slide 4s -2s linear infinite; }

go deeper

for a junior

Know that the animation shorthand's first time is the duration and the second is the delay, and be able to read a declaration like animation: spin 1s linear infinite out loud correctly.

for a middle

Explain that omitted longhands reset to their initial values, name a few of those initials, and describe what goes wrong when a shorthand rule follows a longhand that set fill-mode or play-state.

for a senior

Diagnose the silent failures in review: swapped time values, a shorthand wiping a fill mode across the cascade, and a keyframes name colliding with a shorthand keyword so the animation never binds.

for a principal

Set the convention — prefixed keyframe names, a fixed component order, shorthand over scattered longhands — because these bugs are valid CSS that no linter default flags and they cost real debugging time.

## What the shorthand packs `animation` is shorthand for `animation-name`, `animation-duration`, `animation-timing-function`, `animation-delay`, `animation-iteration-count`, `animation-direction`, `animation-fill-mode` and `animation-play-state`. Because those components have distinguishable value types, the grammar lets you write most of them in any order: ```css .spinner { animation: 1s linear infinite spin; } .spinner { animation: spin 1s linear infinite; } /* identical */ ``` ## The one positional rule: two times Both `animation-duration` and `animation-delay` take a `<time>`, so order is the only thing that can tell them apart. The first time value in the declaration is the duration; a second one is the delay. ```css .a { animation: slide 2s 500ms; } /* 2s long, starts after 0.5s */ .b { animation: slide 500ms 2s; } /* 0.5s long, starts after 2s */ ``` Size is irrelevant — the browser does not "guess" that the bigger number must be the duration. This is the single most common shorthand bug, and it shows up as an animation that appears to never run because it is still sitting in a long delay. A negative delay is legal and useful: `animation: float 4s -2s infinite` starts the animation immediately, already two seconds in. Giving each item in a list a different negative delay is the standard way to desynchronise copies of one animation. ## Omitted longhands reset CSS shorthands are not merges. Anything you leave out is set to its **initial value**, not left at whatever an earlier rule declared: | Longhand | Initial value | | --- | --- | | `animation-name` | `none` | | `animation-duration` | `0s` | | `animation-timing-function` | `ease` | | `animation-delay` | `0s` | | `animation-iteration-count` | `1` | | `animation-direction` | `normal` | | `animation-fill-mode` | `none` | | `animation-play-state` | `running` | So this does not do what it looks like: ```css .card { animation-fill-mode: forwards; animation: fade-out 300ms; /* fill-mode is back to none */ } ``` The fix is to write the fill mode inside the shorthand (`animation: fade-out 300ms forwards`) or to put the shorthand first and the longhand after it. The same trap catches theming setups that set `animation-play-state: paused` in a base rule and then re-declare `animation` in a modifier: the modifier restarts it. The reset also explains why `animation-duration: 0s` is the default. A declaration such as `animation: spin` parses fine but produces no visible motion, because a zero-duration animation completes instantly. ## Names that collide with keywords In the grammar, the keyframes name is the last alternative tried, after every keyword component. If your `@keyframes` name happens to be a value one of those components accepts, the keyword wins: ```css @keyframes running { /* ... */ } .x { animation: running 2s; } /* play-state: running; name: none */ ``` Nothing animates, and the declaration is perfectly valid, which makes it a horrible bug to spot. The names to avoid are the component keywords: `none`, `running`, `paused`, `infinite`, `normal`, `reverse`, `alternate`, `alternate-reverse`, `forwards`, `backwards`, `both`, and the easing keywords `ease`, `linear`, `ease-in`, `ease-out`, `ease-in-out`, `step-start`, `step-end`. Prefixing keyframe names (`anim-fade`, `gi-spin`) sidesteps the whole class of problem. You can also force the issue by using `animation-name: running` as a longhand, where no other component competes for the token. ## Layering several animations The shorthand accepts a comma-separated list, one `<single-animation>` per animation: ```css .hero { animation: fade-in 400ms ease-out both, float 6s ease-in-out 400ms infinite alternate; } ``` Each entry carries its own full set of components, which is far easier to read than the longhand form, where every longhand takes its own comma list and the lists are paired up by position. ## Practical rules Write the shorthand rather than a pile of longhands, so no component is silently reset later. Keep the order `name duration timing delay count direction fill play-state` as a house convention so a reader never has to count time values. Prefix keyframe names. And when an animation "does nothing", check the two time values first — a swapped pair is far more likely than a broken keyframes rule.

  • What does a negative animation-delay do, and why is it useful?
    It starts the animation immediately but with its clock already advanced by that amount, so the element begins partway through the keyframes. Giving a set of elements different negative delays — `-0.1s`, `-0.2s` and so on — desynchronises copies of one looping animation without writing separate `@keyframes` rules. The animationstart event still fires right away, with a non-zero elapsedTime.
  • Why might animation: fade 300ms in one rule and animation-fill-mode: forwards in another produce no fill?
    Because the shorthand resets `animation-fill-mode` to its initial `none`. If the shorthand rule wins the cascade for that element, or simply comes after the longhand in the same rule, the fill mode is gone. Order matters: declare the longhand *after* the shorthand, or better, fold `forwards` into the shorthand itself.
  • How would you write two animations on one element with different durations?
    Use a comma-separated list in the shorthand, one full entry per animation: `animation: fade-in 400ms ease-out both, float 6s ease-in-out infinite alternate;`. Each entry carries its own components. If two entries animate the same property, the later entry in the list wins for that property.

saying these in an interview costs you the question

  • Thinking the larger time value is always the duration
  • Believing a shorthand merges with earlier longhands
  • Assuming component order in the shorthand is fixed
  • Naming a keyframes rule after a shorthand keyword
  • Expecting animation: spin alone to produce motion

context