CSSTiming Functions (ease, cubic-bezier, steps)

Timing Functions (ease, cubic-bezier, steps)

Timing functions control how a transition or animation progresses over time. They determine whether motion is fast, slow, bouncy, or linear. Understanding timing functions is crucial for creating natural, responsive animations that feel right to users.

Common timing function keywords

Keyword

Behavior

Best For

linear

Constant speed

Continuous motion (spinners)

ease

Slow start/end, fast middle

Most UI interactions

ease-in

Slow start, fast end

Exiting/disappearing

ease-out

Fast start, slow end

Entering/appearing

ease-in-out

Slow start/end

Attention-seeking

CSS
<!-- linear: constant speed throughout -->
.element {
  transition: transform 1s linear;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.spinner {
  animation: spin 2s linear infinite;
}

<!-- Smooth constant rotation -->

/* ease: default - starts slow, fast middle, ends slow -->
.button {
  transition: background 0.3s ease;
}

.button:hover {
  background: red;  /* smooth, natural feeling -->
}

<!-- Most natural for UI interactions -->

/* ease-in: starts slow, ends fast (accelerating) -->
.element {
  transition: transform 0.5s ease-in;
}

.element.disappear {
  transform: translateY(100px);
  opacity: 0;
}

<!-- Feels like falling or exiting -->

/* ease-out: starts fast, ends slow (decelerating) -->
.element {
  transition: transform 0.5s ease-out;
}

.element.appear {
  transform: translateY(0);
  opacity: 1;
}

<!-- Feels like landing or entering -->

/* ease-in-out: slow → fast → slow -->
.modal {
  transition: transform 0.6s ease-in-out;
}

.modal.open {
  transform: translateY(0);
}

<!-- Smooth entrance and exit -->
cubic-bezier for custom timing

CSS
<!-- cubic-bezier(x1, y1, x2, y2): custom curve -->
.element {
  transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Presets equivalent to keywords: -->
/* ease: cubic-bezier(0.25, 0.1, 0.25, 1) */
/* ease-in: cubic-bezier(0.42, 0, 1, 1) */
/* ease-out: cubic-bezier(0, 0, 0.58, 1) */
/* ease-in-out: cubic-bezier(0.42, 0, 0.58, 1) */

/* Bouncy timing (overshoot effect) -->
.element {
  transition: transform 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Overshoots then settles -->
}

.element:hover {
  transform: scale(1.1);
}

<!-- Bouncy scale effect -->

/* Slow then fast (aggressive) -->
.element {
  transition: all 0.4s cubic-bezier(0.17, 0.67, 0.83, 0.67);
}

<!-- Snappy transition -->

/* Smooth custom curve -->
.button {
  transition: all 0.3s cubic-bezier(0.45, 0, 0.55, 1);
}

<!-- Personalized easing -->

/* Multiple animations with different timing -->
.card {
  transition:
    transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55),
    box-shadow 0.3s ease,
    opacity 0.2s ease-in-out;
}

.card:hover {
  transform: scale(1.05);
  box-shadow: 0 10px 20px rgba(0,0,0,0.2);
  opacity: 1;
}

<!-- Different timing for different properties -->
Practical timing function patterns

CSS
<!-- Button feedback: instant, then ease out -->
.button {
  transition-property: background, box-shadow, transform;
  transition-duration: 0.1s, 0.3s, 0.3s;
  transition-timing-function: linear, ease-out, ease-out;
}

.button:active {
  background: darkblue;    /* instant -->
  box-shadow: inset 0 2px 4px rgba(0,0,0,0.3);
  transform: scale(0.98);
}

<!-- Button feels responsive -->

/* Hover effects: ease-out for entering -->
.link {
  color: blue;
  text-decoration: none;
  transition: all 0.2s ease-out;
}

.link:hover {
  color: darkblue;
  text-decoration: underline;
}

<!-- Smooth underline appears -->

/* Staggered animations with different timing -->
.item {
  opacity: 0;
  transform: translateY(20px);
  transition: all 0.4s ease-out;
  animation: appear 0.6s ease-out forwards;
}

.item:nth-child(1) { animation-delay: 0s; }
.item:nth-child(2) { animation-delay: 0.1s; }
.item:nth-child(3) { animation-delay: 0.2s; }

@keyframes appear {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

<!-- Items appear sequentially -->

/* Modal entrance: ease-in-out -->
.modal {
  opacity: 0;
  transform: scale(0.9);
  transition: all 0.4s ease-in-out;
}

.modal.open {
  opacity: 1;
  transform: scale(1);
}

<!-- Smooth entrance and exit -->
steps() for discrete animations

CSS
<!-- steps(): animation in discrete steps -->
.element {
  transition: all 0.5s steps(5);
  /* Animation in 5 steps, not smooth -->
}

@keyframes move {
  from { transform: translateX(0); }
  to { transform: translateX(100px); }
}

.element {
  animation: move 1s steps(10);
  /* Movement happens in 10 discrete steps -->
}

<!-- Jumpy, frame-by-frame motion -->

/* step-start and step-end -->
.element {
  transition: opacity 0.3s step-start;
  /* Changes opacity immediately at start -->
}

.element {
  transition: opacity 0.3s step-end;
  /* Changes opacity after delay -->
}

<!-- Useful for specific effects -->

/* Typing effect simulation -->
.text {
  width: 0;
  animation: type 4s steps(20, end) forwards;
  border-right: 2px solid;
}

@keyframes type {
  from { width: 0; }
  to { width: 200px; }
}

<!-- Text appears letter by letter -->

/* Sprite animation with steps -->
.sprite {
  background-image: url('sprite.png');
  background-position: 0 0;
  animation: animate-sprite 0.8s steps(8) forwards;
}

@keyframes animate-sprite {
  to { background-position: -800px 0; }
}

<!-- Sprite animation frame by frame -->
Choosing the right timing function

CSS
<!-- Decision guide -->

/* Material Design default: ease-in-out -->
.material {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  /* Professional, balanced -->
}

/* Apple-style: ease-out -->
.apple-style {
  transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  /* Smooth, polished -->
}

/* Energetic: bouncy cubic-bezier -->
.bouncy {
  transition: all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
  /* Playful, attention-grabbing -->
}

/* Practical guidelines: -->
/* - Page load/entrance: ease-out -->
/* - Hover effects: ease or ease-out -->
/* - Dismiss/exit: ease-in -->
/* - Attention seeking: bounce or ease-in-out -->
/* - Continuous loop: linear -->

/* Testing timing functions -->
/* Use browser DevTools animation inspector -->
/* Preview timing curves before committing -->
/* Adjust values until motion feels natural -->

/* Common timing values library -->
--timing-fast: 0.15s;
--timing-normal: 0.3s;
--timing-slow: 0.5s;

--ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--ease-in: cubic-bezier(0.42, 0, 1, 1);
--ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
Note
Timing functions control animation speed curves. `ease` is best for most UI, `ease-out` for entering effects, `ease-in` for exiting. Use `cubic-bezier()` for custom curves, `steps()` for frame-by-frame animation. Choose timing that matches your design language: professional (ease-in-out), smooth (ease-out), or playful (bouncy).
Next
Transition shorthand: [transition shorthand](/css/transition-shorthand).