Marketing

Testimonial Card

A quote card that rises into place, avatar first.

Frontend engineer
Design systems
It replaced three tools and a
folder of half-finished CSS.
5
Elements
4
Groups
0
Hover states
1180ms
Duration

Why it is timed this way

The reasoning

The card, then the person, then what they said — the order a reader needs it in. Reversing it and animating the quote first leaves the eye somewhere it cannot yet use.

5 elements move, the first at 0ms and the last starting at 280ms — the whole scene settles by 1180ms. Every bar below is one element's animation window.

Avatar
Attribution
Role
Quote 1
Quote 2
0ms590ms1180ms

The code

Everything the scene needs, in whichever format you ship. Entrances compile to@keyframesand hover states totransition, with a reduced-motion guard on both.

188 lines
.testimonial {
  transform: translate3d(0px, 0px, 0);
  width: 960px;
  height: 480px;
  transform-origin: 360px 170px;
}

.quote-card {
  transform: translate3d(0px, 0px, 0);
  box-shadow: 0px 14px 40px 0px #00000059;
  height: 160px;
  background: #171a24;
  border-radius: 18px;
  transform-origin: 306px 116px;
}

.quote-head {
  transform: translate3d(0px, 0px, 0);
  height: 56px;
  transform-origin: 188px 28px;
}

.person {
  transform: translate3d(0px, 0px, 0);
  height: 42px;
  transform-origin: 150px 21px;
}

.avatar {
  transform: translate3d(0px, 0px, 0);
  width: 56px;
  height: 56px;
  background: #6366f1;
  border-radius: 999px;
  animation: avatar-anim 1180ms linear 0ms infinite both;
  will-change: transform, opacity;
}

@keyframes avatar-anim {
  0% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
    animation-timing-function: ease-out;
  }
  59.32% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
  100% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
}

.attribution {
  transform: translate3d(0px, 0px, 0);
  height: 20px;
  background: #00000000;
  color: #98a1b5;
  font-size: 14px;
  font-weight: 500;
  animation: attribution-anim 1180ms linear 0ms infinite both;
  will-change: transform, opacity;
}

@keyframes attribution-anim {
  0% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
  }
  5.93% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
    animation-timing-function: ease-out;
  }
  65.25% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
  100% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
}

.role {
  transform: translate3d(0px, 0px, 0);
  height: 18px;
  background: #00000000;
  color: #4a5164;
  font-size: 13px;
  font-weight: 500;
  animation: role-anim 1180ms linear 0ms infinite both;
  will-change: transform, opacity;
}

@keyframes role-anim {
  0% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
  }
  11.86% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
    animation-timing-function: ease-out;
  }
  71.19% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
  100% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
}

.quote-1 {
  transform: translate3d(0px, 0px, 0);
  height: 30px;
  background: #00000000;
  color: #eceef4;
  font-size: 22px;
  font-weight: 600;
  animation: quote-1-anim 1180ms linear 0ms infinite both;
  will-change: transform, opacity;
}

@keyframes quote-1-anim {
  0% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
  }
  17.8% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
    animation-timing-function: ease-out;
  }
  77.12% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
  100% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
}

.quote-2 {
  transform: translate3d(0px, 0px, 0);
  height: 30px;
  background: #00000000;
  color: #eceef4;
  font-size: 22px;
  font-weight: 600;
  animation: quote-2-anim 1180ms linear 0ms infinite both;
  will-change: transform, opacity;
}

@keyframes quote-2-anim {
  0% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
  }
  23.73% {
    transform: translate3d(0px, 48px, 0);
    opacity: 0;
    animation-timing-function: ease-out;
  }
  83.05% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
  100% {
    transform: translate3d(0px, 0px, 0);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .avatar,
  .attribution,
  .role,
  .quote-1,
  .quote-2 {
    animation: none;
    transition: none;
  }
}

More scenes

Starter
$0
Pro
$12
Team
$40

Three-Plan Pricing

Three plans that pop in, with the featured column raised and hover lifts on every card.

Canvas
Timeline
Easing
States
Export
Import

Feature Tile Grid

Six tiles arriving as a wave across the grid, each lifting on hover.

58
motion presets
17
components
12
export formats
0
accounts

Stat Row

Four figures dropping in with a small overshoot.