A little help goes a long way. Find a tool/

Design FIELD NOTES · 4 MIN READ

CSS Transitions Explained: Timing, Easing and Delay

How the transition shorthand works: properties, duration, timing functions from ease to steps, and why transitions need a state change to fire.

A transition is the browser turning a sudden change into a smooth one. Change a button's background on hover and, without a transition, the color snaps; with transition: background-color 200ms, the browser fills in every intermediate frame. The whole feature lives in one property with four ingredients — and getting those four right is the difference between UI that feels fluid and UI that feels laggy or twitchy.

One property, four ingredients

The shorthand reads: transition: property duration timing-function delay. Only the duration is mandatory — the property defaults to all, the timing function to ease, and the delay to 0s. So transition: 300ms is legal and means "animate every animatable property over 300 milliseconds with the default curve".

Multiple comma-separated groups give different properties different timing, and the order matters within each group:

transition: opacity 200ms ease-out, transform 400ms cubic-bezier(0.2, 0.8, 0.2, 1) 100ms;

Here the fade finishes fast; the movement starts 100ms later and takes longer. A common mistake is listing a property without a duration — transition: opacity, transform 300ms is valid syntax, but the durationless opacity group is treated as 0ms and snaps instantly. Every property needs its own duration.

Timing functions: the shape of the motion

The timing function is a curve that maps time (x-axis, 0 to 1) to progress (y-axis). The keyword set covers the everyday choices: linear for constant speed, ease as the browser's gentle default, ease-in starting slow, ease-out ending slow, and ease-in-out for both. Beneath the keywords, everything is a cubic Bézier: ease is literally cubic-bezier(0.25, 0.1, 0.25, 1).

The Bézier form is where expressive motion lives. The two x values (control points on the time axis) must stay between 0 and 1 — you cannot travel backward in time. The y values (progress axis) may go outside that range, and that is how overshoot is made: cubic-bezier(0.34, 1.56, 0.64, 1) briefly passes 100% progress before settling, which reads as a springy landing. Material-style entrances use curves like cubic-bezier(0.4, 0, 0.2, 1) — accelerate hard, decelerate gently.

steps(): motion in slices

Not every animation should be smooth. steps(n, jump-end) divides the change into n discrete frames, holding each value before jumping to the next. This is the trick behind sprite-sheet characters (step through one row of frames), typewriter reveals (step a width through per-character jumps), and retro tickers. The jump position decides where within each slice the jump happens: jump-start changes immediately, jump-end at the end of each slice, jump-none and jump-both handle the edge cases. The legacy shorthand keywords step-start and step-end are steps(1) with those positions.

Transitions need a state change

A transition fires when a property's computed value changes after the element is rendered — via :hover, :focus, a class toggle, or a style mutation. It does not fire on initial page load: if the transition is declared in the same rule that sets the starting values, the browser renders once with everything in place and no change ever occurs. Related trap: declaring the transition only inside :hover means the enter animation runs but the exit snaps, because when the pointer leaves, the :hover rule (and its transition) no longer applies. Put the transition on the base rule so both directions animate.

When the choreography gets complex — loops, delays between keyframes, holding mid-values — that is @keyframes territory. The division of labor: transitions react to state changes; animations play on their own schedule.

Transition cheap things

Every transitioned property that affects layout — width, height, top, padding — makes the browser recompute layout and repaint for every frame of the animation. The two properties that skip all of that are transform and opacity, which the compositor can animate without touching layout at all. A "grow on hover" card is smoother implemented as transform: scale(1.03) than as a width change. Durations have conventions too: 150–250ms for hover feedback, 300–500ms for panels and modals. Anything past about a second starts to feel sluggish for UI feedback.

Try it yourself

The CSS Transition Generator builds the shorthand field by field — properties, duration, keywords, custom cubic-bezier curves and steps — and includes a demo box you can animate to feel each easing curve. Pair it with the CSS Filter Generator to see transitions animate filter effects, and the Font Size Clamp Generator when the text itself, not just its container, should respond smoothly to the viewport.

Frequently asked questions

What is the CSS transition shorthand?
transition: <property> <duration> <timing-function> <delay>. Only the duration is mandatory; the property defaults to all, the timing function to ease and the delay to 0s. Multiple comma-separated groups transition different properties with different timing.
Why does my transition not run?
The most common causes: the property is not animatable (display, font-family), the transition is declared only on the :hover state (it must be on the base rule so the return trip animates too), or the value change happens without a state change, such as on page load.
What does cubic-bezier(x1, y1, x2, y2) mean?
It describes a curve of progress over time. x is time (0 to 1) and y is progress; the y values may leave that range to overshoot, producing bounce. ease is cubic-bezier(0.25, 0.1, 0.25, 1) and linear is a straight line.
When should I use steps() instead of a curve?
When the motion should be choppy on purpose: sprite-sheet frames, typewriter reveals, tickers. steps(n, jump-end) divides the change into n discrete frames, holding each value before jumping to the next.
Transition or animation — which should I use?
Transitions react to a state change (hover, focus, a class toggle) and run once per change. @keyframes animations run on their own schedule, can loop and can hold intermediate keyframes. Use transitions for feedback and animations for choreography.