Weekend in Lisbon
Three days of tiles, tarts and trams.
A customer quote card: the big quote mark swings into the accent color and the five stars pop in one after another. The star shape is a clip-path polygon, so there is no icon font.
Also known as: review card hover effect, quote card css, customer testimonial animation.
Setup took ten minutes, and support answered before my coffee cooled.
<figure class="hfx-card-testimonial">
<svg class="hfx-card-testimonial-quote" viewBox="0 0 32 24" aria-hidden="true"><path d="M0 24V14C0 6 4 1.2 12 0v5c-4 1-6 4-6 8h6v11zm18 0V14c0-8 4-12.8 12-14v5c-4 1-6 4-6 8h6v11z"/></svg>
<div class="hfx-card-testimonial-stars" role="img" aria-label="Rated 5 out of 5">
<span style="--i: 0"></span><span style="--i: 1"></span><span style="--i: 2"></span><span style="--i: 3"></span><span style="--i: 4"></span>
</div>
<blockquote>Setup took ten minutes, and support answered before my coffee cooled.</blockquote>
<figcaption><span class="hfx-card-testimonial-avatar"></span><span><strong>Jo Park</strong> Founder, Loop</span></figcaption>
</figure>.hfx-card-testimonial {
--c: var(--hfx-color, #3b82f6);
font-size: var(--hfx-size, 16px);
width: 15em;
border-radius: 0.75em;
overflow: hidden;
background: #fff;
border: 1px solid color-mix(in srgb, #000 10%, transparent);
color: #1f2937;
font-family: system-ui, sans-serif;
position: relative;
box-sizing: border-box;
width: 16em;
margin: 0;
padding: 1.1em 1.2em 1.1em;
transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.35s ease;}
.hfx-card-testimonial:hover {
transform: translateY(-0.2em);
box-shadow: 0 0.9em 1.4em -0.9em color-mix(in srgb, #000 35%, transparent);
}
.hfx-card-testimonial-quote {
position: absolute;
top: 0.85em;
right: 1.1em;
width: 1.8em;
fill: color-mix(in srgb, var(--c) 20%, transparent);
transform-origin: 50% 60%;
transition: fill 0.3s ease, transform 0.55s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.hfx-card-testimonial:hover .hfx-card-testimonial-quote {
fill: var(--c);
transform: translate(-0.1em, -0.05em) rotate(-10deg) scale(1.15);
}
.hfx-card-testimonial-stars {
display: flex;
gap: 0.18em;
margin-bottom: 0.7em;
}
.hfx-card-testimonial-stars span {
width: 0.85em;
height: 0.85em;
background: color-mix(in srgb, var(--c) 22%, transparent);
clip-path: polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
}
.hfx-card-testimonial:hover .hfx-card-testimonial-stars span {
background: var(--c);
animation: hfx-card-testimonial-pop 0.45s calc(var(--i) * 70ms) cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
.hfx-card-testimonial blockquote {
margin: 0;
font-size: 0.85em;
line-height: 1.45;
color: #374151;
}
.hfx-card-testimonial figcaption {
display: flex;
padding: 0;
font-family: inherit;
align-items: center;
gap: 0.6em;
margin-top: 0.8em;
font-size: 0.72em;
color: #6b7280;
}
.hfx-card-testimonial figcaption strong {
display: block;
color: #111827;
}
.hfx-card-testimonial-avatar {
width: 2.4em;
height: 2.4em;
border-radius: 50%;
background: linear-gradient(135deg, color-mix(in srgb, var(--c) 45%, #fff), var(--c));
}
@keyframes hfx-card-testimonial-pop {
from { transform: scale(0.4) rotate(-40deg); background: color-mix(in srgb, var(--c) 22%, transparent); }
to { transform: none; background: var(--c); }
}
@media (prefers-reduced-motion: reduce) {
.hfx-card-testimonial, .hfx-card-testimonial *, .hfx-card-testimonial::before, .hfx-card-testimonial::after, .hfx-card-testimonial *::before, .hfx-card-testimonial *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--hfx-color on the element or a parent.
Currently previewing #3b82f6.--hfx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.