transitions & timing functions

[check: …]
📖 Pair this live Tailwind demo with the companion guide (.md) — this page is the rendered ground truth. ↗ Pairs with /tailwind/keyframes_animate (transitions interpolate between two states; keyframes animate through many).

a transition is three knobs you compose

A Tailwind transition is not one utility — it's three independent CSS properties composed together. Get any one wrong and the effect breaks in a confusing way. WHICH properties animate (transition-colors), HOW LONG (duration-300), and the SHAPE of the curve (ease-out). Optionally a fourth: WHEN (delay-150) and a PERF HINT (will-change-transform).

knobtailwind utilitycss propertydefault if omitted
which properties transition · transition-colors · transition-transform · transition-opacity · transition-shadow · transition-none transition-property transition-property: nonenothing animates
how long duration-150 · duration-300 · duration-700 · duration-[250ms] transition-duration 0ssnaps instantly (even with property set)
shape of curve ease-linear · ease-in · ease-out · ease-in-out · ease-[cubic-bezier(0.4,0,0.2,1)] transition-timing-function ease (= cubic-bezier(0.25,0.1,0.25,1))
when to start delay-150 · delay-300 · delay-[1s] transition-delay 0s (starts immediately)
perf hint will-change-auto · will-change-transform · will-change-[opacity] will-change auto (browser guesses; usually fine)

Plain transition (no suffix) is the "common set": color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter. It's the safe default. transition-all uses the literal token all — heavy and discouraged: it can thrash layout when an unrelated property (e.g. width) changes mid-transition.

1 · hover buttons — which properties animate

Same hover target on each card (bg color change + scale + fade), but each button allows only ONE property group to animate. Hover each one and watch what moves vs. what snaps. The transition-none card is the control: everything snaps.

The first three cards each show ONE thing animating while the other two hover effects snap. The fourth (transition) animates all three because transform, opacity, and the color properties are all in the common set.

2 · duration race — 150ms vs 300ms vs 700ms

Click Play to send all three runners across their tracks at once. They use the same easing (ease-linear) but different duration-*. The 150ms runner finishes first; 700ms finishes last. This is the knob that controls perceived speed.

duration-150
duration-300
duration-700

click Play to start the race

Default gotcha: if you forget duration-*, the duration is 0s and the runner snaps to the end with no visible transition — even though transition-transform is set. Many "my transition doesn't work" bugs are a missing duration.

3 · easing comparison — shape of the curve

Same duration (duration-700) for all four runners, but each uses a different transition-timing-function. Linear moves at constant speed; ease-in starts slow; ease-out ends slow (feels natural for UI enter); ease-in-out is slow at both ends. The custom cubic-bezier(0.34,1.56,...) overshoots.

ease-linear
ease-in
ease-out
ease-in-out
ease-[cubic-bezier(0.34,1.56,0.64,1)] (overshoot)

click Play to start the race

Rule of thumb: entering UI elements use ease-out or an overshoot bezier; exiting elements use ease-in. ease-in-out is for state toggles that go both directions. Avoid ease-linear for organic motion — it reads as mechanical.

4 · delay stagger — same animation, sequenced start

delay-* offsets the start of a transition (not the duration). With the same duration and easing, increasing delays create a staggered "cascade" — the classic list-reveal or menu-open feel. Click Play to see all five runners leave the start line in sequence.

delay-0 (no delay)
delay-150
delay-300
delay-500
delay-700

click Play to start the cascade

Tip: for long lists, compute the delay per item with inline style="transition-delay: calc(var(--i) * 50ms)" rather than authoring delay-50delay-500 by hand. For looping/entrance animations, prefer keyframes + animation-delay.

intent → pattern

intentpatternwhy
smooth color/bg/border hover transition-colors duration-200 scoped to color props — cheapest, no layout/paint thrash
card lift on hover transition-transform duration-300 ease-out + hover:-translate-y-1 transform is GPU-composited; ease-out feels like it "settles"
fade in/out (modal, tooltip) transition-opacity duration-200 only opacity animates — pairing with pointer-events-none on hidden
spring/overshoot pop transition duration-500 ease-[cubic-bezier(0.34,1.56,0.64,1)] 2nd control-y > 1 → overshoots past 100% then settles
staggered list reveal transition duration-300 ease-out + per-item delay-75delay-300 same animation, offset starts — feels designed, not mechanical
disable a transition conditionally transition-none (or media query: motion-reduce:transition-none) respect prefers-reduced-motion for accessibility
perf hint for a known-heavy animation will-change-transform on the element only set it right before & remove after — permanent will-change wastes RAM
custom duration/easing tokens @theme { --ease-spring: cubic-bezier(0.34,1.56,0.64,1); }ease-spring v4 reads timing-function + duration tokens from @theme