animation-timing-function
Controlling progress between animation keyframes.
Overview
| Class | Declarations |
|---|---|
animation-timing-function-smooth | animation-timing-function: var(--easing-smooth);
|
animation-timing-function-soft | animation-timing-function: var(--easing-soft);
|
animation-timing-function-crisp | animation-timing-function: var(--easing-crisp);
|
animation-timing-function-snap | animation-timing-function: var(--easing-snap);
|
animation-timing-function-accelerate | animation-timing-function: var(--easing-accelerate);
|
animation-timing-function-decelerate | animation-timing-function: var(--easing-decelerate);
|
animation-timing-function-overshoot | animation-timing-function: var(--easing-overshoot);
|
animation-timing-function-rewind | animation-timing-function: var(--easing-rewind);
|
animation-timing-function-spring | animation-timing-function: var(--easing-spring);
|
animation-timing-function:ease | animation-timing-function: ease;
|
animation-timing-function:ease-in | animation-timing-function: ease-in;
|
animation-timing-function:ease-out | animation-timing-function: ease-out;
|
animation-timing-function:ease-in-out | animation-timing-function: ease-in-out;
|
animation-timing-function:linear | animation-timing-function: linear;
|
animation-timing-function:step-start | animation-timing-function: step-start;
|
animation-timing-function:step-end | animation-timing-function: step-end;
|
animation-timing-function:<function>,<…> | animation-timing-function: <function>,<…>;
|
Examples
Set keyframe easing
Compare linear, ease-in and ease-out over the same one-second path. Replay starts all three native timelines together.
@theme { @keyframes layer-slide { from { transform: translateX(24px); } to { transform: translateX(96px); } }}<!-- Linear · even progress --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:forwards animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div><!-- Ease-in · slower start --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:forwards animation-name:layer-slide animation-timing-function:ease-in animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div><!-- Ease-out · slower finish --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:forwards animation-name:layer-slide animation-timing-function:ease-out animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div>Generated CSS
@layer utilities { .animation-timing-function\:ease-out { animation-timing-function: ease-out }}An easing function redistributes progress between keyframe stops. It does not change the declared duration or endpoints.
Use linear timing for rotation
Compare a constant-speed turn with ease-in-out. Both have a two-second duration and infinite count.
<!-- Linear · steady turn --><div class="flex items-center gap-md p-sm"> <svg id="target" aria-hidden="true" width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" class="text-blue animation-duration:2s animation-iteration-count:infinite animation-name:rotate animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion"> <circle cx="24" cy="24" r="17" opacity=".25" /> <path d="M24 8v16h12 M19 13l5-5 5 5" /> </svg> <span class="font-sm">Linear · steady turn</span></div><!-- Ease-in-out · changing speed --><div class="flex items-center gap-md p-sm"> <svg id="target" aria-hidden="true" width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2" class="text-blue animation-duration:2s animation-iteration-count:infinite animation-name:rotate animation-timing-function:ease-in-out animation:none@print animation-play-state:paused@reduce-motion"> <circle cx="24" cy="24" r="17" opacity=".25" /> <path d="M24 8v16h12 M19 13l5-5 5 5" /> </svg> <span class="font-sm">Ease-in-out · changing speed</span></div>The linear hand maintains angular speed across cycles. Ease-in-out slows near each cycle boundary, even though a full turn ends at the same visual orientation.
Match easing to intent
For a deliberately stepped movement, place a timing function on the starting keyframe. It controls the interval leading to the next keyframe.
@theme { @keyframes layer-slide { from { transform: translateX(24px); animation-timing-function:steps(4,end); } to { transform: translateX(96px); } }}<!-- Keyframe easing · four 18px steps --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:forwards animation-name:layer-slide animation-timing-function:linear animation:none@print animation-play-state:paused@reduce-motion">→</span> </div></div>The element declares linear, but the keyframe’s steps(4,end) overrides it for this interval. Easing on the final 100% keyframe has no following interval to affect.
Apply conditionally
Resize before replaying to switch from ease-out to linear at sm. The one-second duration and 72px path remain unchanged.
@theme { @keyframes layer-slide { from { transform: translateX(24px); } to { transform: translateX(96px); } }}<!-- Ease-out → linear at sm --><div class="w:240px max-w:100%"> <p class="mb-sm font-mono font-xs text-muted">Base 0 · start 24 · end 96px</p> <div aria-hidden="true" class="h:56px p-xs outline:1px|dashed|var(--color-line-muted)"> <span id="target" class="grid place-items:center h:40px w:48px b:1px|solid|var(--color-blue-60) r-sm bg-blue-10 fg-blue-80 transform:none animation-duration:1s animation-fill-mode:forwards animation-name:layer-slide animation-timing-function:ease-out animation:none@print animation-play-state:paused@reduce-motion animation-timing-function:linear@sm">→</span> </div></div>The native query changes the element’s easing. A keyframe-level easing would take precedence for its own interval; these keyframes contain no such override.