Articles / Animation & Motion /
Transition shorthand: order matters
The CSS transition shorthand has a strict order for duration and delay. Learn the rule that prevents one-second delays from appearing where you expected one-second durations.
Why the order is duration-first
The CSS spec designers chose duration-first for a practical reason. Duration is the essential value; delay is optional. Most transitions don't need a delay. By putting duration first, the minimum valid shorthand is transition: property duration. Two values are enough to create a working transition.
If delay came first, you'd need to include an explicit 0s for every transition that doesn't need delay: transition: property 0s duration ease. That adds unnecessary verbosity to every single transition declaration. The current syntax keeps the common case minimal, only requiring extra characters when you need the less-common delay feature.
Another reason: duration is more commonly adjusted than delay. During design iterations, you'll tweak the transition length to match the interaction's feel. Delay is more specialized, used only for staggered animations or avoiding jarring immediate responses. Putting the more-critical value first reflects its priority in the syntax hierarchy. It's the same principle as function parameters in programming: most common parameters come first.
The transition shorthand syntax
The full transition shorthand accepts up to four values in a specific order: property name, duration, timing function, and delay. The syntax is:
transition: property duration timing-function delay;
Where:
propertyis the CSS property to transition (orall)durationis how long the transition takes (required, first time value)timing-functionis the easing curve likeeaseorcubic-bezier()delayis how long to wait before starting (second time value, optional)
The browser parses this left to right. The first time it sees becomes duration. The second time becomes delay. There is no ambiguity because the order is fixed.
Common mistakes and corrections
Let's look at some typical errors. You want a 2-second fade with a 0.5-second delay. You write transition: opacity 2s ease 0.5s. This is correct—duration first, then delay. But what if you wrote transition: opacity 0.5s ease 2s? The browser sees a 0.5-second duration and a 2-second delay. Your button waits two seconds, then fades in half a second. This is backwards from what you intended.
Another common error: mixing up the order when adding delay late in development. You start with transition: all 0.3s ease. Later you add a delay: transition: all 0.3s ease 0.1s. This is correct. But if you accidentally swap them: transition: all 0.1s ease 0.3s, you now have a very fast transition that waits 0.3 seconds to start. The transition appears unresponsive because 0.1 seconds is almost instantaneous. Users report the interface feeling "jumpy" or "broken" when transitions don't match their expectations.
What about when you use three time values? transition: background-color 0.3s 0.1s ease 0.2s This is invalid. The browser expects at most two time values in a single transition entry. When it sees more than two, it treats the first two as duration and delay, then throws away the rest. The last 0.2s is ignored, creating silent failures that are hard to debug.
When debugging, always ask: which is the first time value? If it's smaller than you expected, you may have accidentally written the delay as the duration. Use DevTools to inspect the computed style rather than relying on what you wrote in the source. The browser's cascade viewer will show exactly how it interpreted your shorthand.
Multiple transitions with different delays
When transitioning multiple properties, each can have its own duration and delay. The shorthand accepts comma-separated lists:
.card {
transition:
transform 0.3s ease,
opacity 0.5s ease 0.1s,
box-shadow 0.4s cubic-bezier(0.2, 0, 0, 1) 0.2s;
}
.card:hover {
transform: translateY(-5px);
opacity: 0.9;
box-shadow: 0 10px 30px oklch(0 0 0 / 0.15);
}
On hover, transform starts immediately and takes 0.3 seconds. opacity waits 0.1 seconds, then transitions over 0.5 seconds. box-shadow waits 0.2 seconds, then transitions over 0.4 seconds. This staggered timing creates a cascading animation effect where properties change in sequence rather than all at once.
Notice how each entry follows the same order: duration always comes before delay. The timing function is optional for each property, but duration is always required.
Timing functions and their role
The timing function, while not a time value, plays a crucial role in the shorthand. It sits between the two time values. The valid positions for a timing function are ease, ease-in, ease-out, ease-in-out, linear, or a functional notation like cubic-bezier() or steps(). The browser uses this to parse the order correctly.
Consider transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0.1s. The browser sees: 0.3s (first time = duration), cubic-bezier(...) (this is a timing function, not a time), and 0.1s (second time = delay). The timing function acts as a marker that separates the two time values. That's why transition: opacity 0.3s 0.1s ease works—0.3s is duration, 0.1s is delay, and ease is the timing function.
But transition: opacity ease 0.3s 0.1s doesn't work as expected. The browser interprets ease as the property (which is invalid), 0.3s as duration, and 0.1s as delay—resulting in no transition because "ease" is not a valid CSS property to animate. The order isn't just about time values; everything has a fixed position in the shorthand.
Testing your understanding
Here are three transition declarations. Can you identify which have the correct order for their intent? First: transition: transform 0.25s ease-in-out 0.5s. Intent: a quarter-second transform that waits half a second before starting. This is correct—duration (0.25s) comes before delay (0.5s).
Second: transition: opacity 0.4s 0.4s ease. Intent: a 0.4-second fade with no delay. This looks wrong at first glance—why would you delay for 0.4 seconds and then animate for 0.4 seconds? Unless you specifically want a stagger effect, you probably meant transition: opacity 0.4s ease with no delay. Watch out for accidental second time values.
Third: transition: background-color ease 0.3s. Intent: a three-tenths-second color transition starting immediately. This is invalid—the browser can't parse it correctly because ease is in the duration position. Duration must come before timing function. Fix it by writing transition: background-color 0.3s ease.
Use these as mental exercises when reviewing code. Can you instantly read a transition and understand what it does? If not, the syntax might be unclear or wrong. Clear code prevents bugs.
The relationship with longhand properties
Understanding the shorthand requires understanding what it expands to. The transition shorthand controls four longhand properties: transition-property, transition-duration, transition-timing-function, and transition-delay. When you write the shorthand, you're setting all four at once. When you write the longhands, you're setting them individually.
The shorthand has a critical quirk: any longhand property you omit gets reset to its initial value. So transition: background-color 0.3s ease actually means transition-property: background-color; transition-duration: 0.3s; transition-timing-function: ease; transition-delay: 0s. The delay is reset to zero because you didn't specify it. This is different from how some other CSS shorthands work, where omitted values keep their existing values.
This reset behavior can cause surprises. Imagine you've set up a staggered animation with transition-delay: 0.1s on a class, then later you add a transition shorthand to the same element: transition: opacity 0.3s ease. Your delay gets reset to zero. The stagger is gone. To avoid this, always specify the delay in the shorthand if you need it, or use longhand properties instead of mixing approaches.
Browser support and polyfills
The transition shorthand has been supported since the early days of CSS animations. All modern browsers—Chrome, Firefox, Safari, Edge—follow the same duration-first rule. There are no browser-specific quirks or exceptions. If your transition behaves unexpectedly, it's always a syntax error, not a browser bug. This consistency is one of the most reliable aspects of CSS animations.
For older browsers like Internet Explorer 9 and below, transitions are not supported at all. These browsers simply ignore the transition property and show instant state changes. There is no polyfill for transitions; the feature is too fundamental to CSS for a JavaScript workaround to make sense. Polyfilling would require intercepting every style change and manually animating, defeating the purpose of using CSS transitions.
In 2026, transitions are a Baseline feature with support extending back to 2012. Any browser still in use supports them. The only consideration is testing your timing values across browsers: rendering engines may animate at slightly different frame rates, but the duration and delay values remain consistent. A 0.3-second transition in Chrome is a 0.3-second transition in Safari. The consistency you need is guaranteed.
When to use longhand properties
Use the longhand properties—transition-property, transition-duration, transition-timing-function, transition-delay—when clarity is more important than brevity. This happens in three situations: complex animations with staggered delays, documentation-heavy codebases, and when you need to modify one property without touching the others.
Here's how the longhand equivalent of our earlier card example looks:
.card {
transition-property: opacity, transform, box-shadow;
transition-duration: 0.3s, 0.3s, 0.4s;
transition-timing-function: ease, cubic-bezier(0.2, 0, 0, 1), ease;
transition-delay: 0s, 0.1s, 0.15s;
}
This is verbose but explicit. Each longhand property controls all transitioned properties in sequence. The first duration (0.3s) applies to opacity, the second (0.3s) to transform, the third (0.4s) to box-shadow. If you want different durations, this is the only way to specify them—except via multiple transition entries in the shorthand.
The longhand format is particularly useful when using CSS custom properties for animating lists. You can set transition-delay: calc(0.05s * var(--i)) without affecting other properties. This dynamic calculation is harder to express in shorthand syntax.
The role of transition-property
The first value in the transition shorthand is the property to animate. Common choices include specific properties like opacity, background-color, or transform. Using all means every animatable property on the element will transition when changed. Each approach has trade-offs.
Specifying properties explicitly is more predictable. transition: background-color 0.3s ease only transitions background color. If you later add border-color changes, they'll happen instantly. This explicit mode helps catch unintended styling changes. When you add a new property and it doesn't transition, you know you need to update the transition list deliberately.
Using all is more convenient but riskier. transition: all 0.3s ease covers any property change without updating CSS. But it can cause performance issues because the browser must track every property for changes. It can also create unexpected behavior: you change the font-size of a button, and suddenly all the typography animates, creating a jarring effect. Use all only for simple, contained components where you control all the changing properties.
Multiple properties are written as comma-separated lists: transition: transform 0.3s, opacity 0.3s 0.1s. Notice each property gets its own time values. This is the most explicit shorthand form—more readable than all, less verbose than four longhand properties.
Common patterns and pitfalls
Several patterns appear frequently in production code. The first is the "instant then fade" pattern: you want an element to appear instantly but fade in over time. This is impossible with a single transition because the element needs to exist to transition. Instead, use opacity 0 initially, then change opacity. The transition will animate from 0 to 1 over the specified duration.
The second common pitfall is forgetting that transition-delay is per-transition, not cumulative. transition: opacity 0.3s 0.1s, transform 0.3s 0.2s doesn't mean transform waits 0.3s (0.1s + 0.2s); it means opacity waits 0.1s, transform waits 0.2s. Both start counting from the same trigger moment. This is important for understanding stagger effects.
The third pattern is negative delays. You can set transition-delay: -0.1s. This makes the transition start immediately but begin partway through its duration. The effect is an animation that appears to have already started. Used carefully, this can create interesting "mid-transition" states, though it's rarely the right choice for production UI.
The complete transition lifecycle
Understanding the full lifecycle of a transition helps clarify how delay interacts with duration. When a state change occurs, the browser goes through these stages: first, if there's a delay, the browser waits. Second, once the delay expires, the browser starts interpolating from the old value to the new one over the duration period. Third, when the duration completes, the transition is done and the element rests in its final state.
If the element returns to its original state during a delay, nothing happens. The transition was pending and never started. If the element returns to its original state during the duration, the browser reverses the interpolation—from its midway point back to the original. This reversal behavior is why transitions feel so natural: they respond instantly to state changes, unlike keyframe animations that must complete or require manual pause/play control.
The delay doesn't queue up like a timeout in JavaScript. Each state change resets the delay counter. Add a class, wait, remove the class—the delay starts over. This matters for rapidly toggling states like hover or focus. You won't get a cascading effect where delays accumulate; each toggle is fresh.
Summary and key takeaways
The CSS transition shorthand is simple but has one critical rule that trips up even experienced developers: the first time value is always the duration, and the second time value is always the delay. There is no ambiguity, no browser-specific behavior—just a fixed order that every browser follows. Once this becomes muscle memory, the shorthand becomes second nature.
Remember the syntax: transition: property duration timing-function delay. The timing function is optional, the delay is optional, but the duration is required. When present, delay must come after duration. When in doubt, check the computed style in DevTools to verify how your browser interpreted the shorthand.
Use delays thoughtfully. They're powerful for creating polished, cascading effects in UI components. But don't overuse them: simple hover states don't need staggering, and excessive delays make interfaces feel sluggish. When combined with proper easing and reasonable durations, delays become an invisible craftsperson—refining the animation without drawing attention to itself. The best transitions feel natural, not engineered.
Practical strategy
When writing transitions, follow this checklist: first, write the duration. Second, add the timing function if needed. Third, add delay only if you want a stagger. If you're unsure, test by temporarily setting extreme values: transition: background-color 5s ease 2s. The five-second duration and two-second delay will make it obvious which is which. Then adjust to production values.
Consider documenting delay patterns in your design system. If your team commonly uses staggered transitions, create documented examples showing the correct order. This reduces the chance of individual developers making the same mistake. A style guide entry like "staggered transition: opacity 0.4s ease 0.1s" becomes a reference point everyone can copy.
Finally, embrace explicit over implicit when complexity grows. Five properties with different delays are hard to read in shorthand. Use individual properties for clarity. Simple transitions get the shorthand. Complex ones get structure. Both approaches produce valid CSS; choose based on maintainability.