22 CSS Image Carousels06 / 22
Testimonial Card Carousel
A swipeable rail of review cards — each with a star rating, quote and avatar — snapping neatly one per view on mobile and two-up on wider screens. The social-proof carousel for landing pages, built on scroll-snap with no library.
Published
The code
<section class="car-06" aria-label="Customer testimonials">
<ul class="car-06__rail" tabindex="0" role="group" aria-label="Customer reviews, scrollable">
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" alt=""><span><b>Priya N.</b><small>Front-end Lead, UK</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 4.5 out of 5"><span class="car-06__fill" style="width:90%"></span></div>
<p class="car-06__quote">"Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" alt=""><span><b>Marcus T.</b><small>Web Dev, US</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" alt=""><span><b>Alira K.</b><small>UX Engineer, AU</small></span></div>
</li>
</ul>
</section><section class="car-06" aria-label="Customer testimonials">
<ul class="car-06__rail" tabindex="0" role="group" aria-label="Customer reviews, scrollable">
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" alt=""><span><b>Priya N.</b><small>Front-end Lead, UK</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 4.5 out of 5"><span class="car-06__fill" style="width:90%"></span></div>
<p class="car-06__quote">"Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" alt=""><span><b>Marcus T.</b><small>Web Dev, US</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" alt=""><span><b>Alira K.</b><small>UX Engineer, AU</small></span></div>
</li>
</ul>
</section>.car-06,
.car-06 *,
.car-06 *::before,
.car-06 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-06 ::selection {
background: oklch(0.62 0.15 285);
color: #fff;
}
.car-06 {
--accent: oklch(0.62 0.15 285);
--star: #f5a623;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 12px;
background: #f5f4f9;
display: flex;
align-items: center;
justify-content: center;
}
.car-06__rail {
list-style: none;
display: flex;
gap: 18px;
width: min(760px,100%);
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding: 10px;
padding: 10px;
scrollbar-width: thin;
scrollbar-color: #d5d2e2 transparent;
}
.car-06__rail:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
border-radius: 16px;
}
.car-06__card {
flex: 0 0 clamp(240px,82%,320px);
scroll-snap-align: center;
background: #fff;
border: 1px solid #e9e7f1;
border-radius: 18px;
padding: 24px 22px;
box-shadow: 0 16px 34px -28px rgba(40,30,80,.7);
transition: transform .25s;
}
.car-06__card:hover {
transform: translateY(-4px);
}
.car-06__stars {
position: relative;
width: 112px;
height: 18px;
-webkit-mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
background: #e4e2ee;
}
.car-06__fill {
position: absolute;
inset: 0;
background: var(--star);
display: block;
}
.car-06__quote {
margin: 16px 0 20px;
font-size: 15px;
line-height: 1.55;
color: #33303d;
text-wrap: pretty;
}
.car-06__who {
display: flex;
align-items: center;
gap: 12px;
}
.car-06__ava {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
flex: none;
background: #e4e2ee;
}
.car-06__who b {
display: block;
font-size: 14px;
color: #26232f;
}
.car-06__who small {
display: block;
font-size: 12px;
color: #8b8797;
margin-top: 1px;
}
@media (min-width: 600px) {
.car-06__card {
flex-basis: calc((100% - 18px)/2);
}
}
@media (prefers-reduced-motion: reduce) {
.car-06__card {
transition: none;
}
.car-06__rail {
scroll-behavior: auto;
}
}.car-06,
.car-06 *,
.car-06 *::before,
.car-06 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-06 ::selection {
background: oklch(0.62 0.15 285);
color: #fff;
}
.car-06 {
--accent: oklch(0.62 0.15 285);
--star: #f5a623;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 12px;
background: #f5f4f9;
display: flex;
align-items: center;
justify-content: center;
}
.car-06__rail {
list-style: none;
display: flex;
gap: 18px;
width: min(760px,100%);
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding: 10px;
padding: 10px;
scrollbar-width: thin;
scrollbar-color: #d5d2e2 transparent;
}
.car-06__rail:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
border-radius: 16px;
}
.car-06__card {
flex: 0 0 clamp(240px,82%,320px);
scroll-snap-align: center;
background: #fff;
border: 1px solid #e9e7f1;
border-radius: 18px;
padding: 24px 22px;
box-shadow: 0 16px 34px -28px rgba(40,30,80,.7);
transition: transform .25s;
}
.car-06__card:hover {
transform: translateY(-4px);
}
.car-06__stars {
position: relative;
width: 112px;
height: 18px;
-webkit-mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
background: #e4e2ee;
}
.car-06__fill {
position: absolute;
inset: 0;
background: var(--star);
display: block;
}
.car-06__quote {
margin: 16px 0 20px;
font-size: 15px;
line-height: 1.55;
color: #33303d;
text-wrap: pretty;
}
.car-06__who {
display: flex;
align-items: center;
gap: 12px;
}
.car-06__ava {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
flex: none;
background: #e4e2ee;
}
.car-06__who b {
display: block;
font-size: 14px;
color: #26232f;
}
.car-06__who small {
display: block;
font-size: 12px;
color: #8b8797;
margin-top: 1px;
}
@media (min-width: 600px) {
.car-06__card {
flex-basis: calc((100% - 18px)/2);
}
}
@media (prefers-reduced-motion: reduce) {
.car-06__card {
transition: none;
}
.car-06__rail {
scroll-behavior: auto;
}
}/* No JavaScript — scroll-snap rail; star ratings are a masked gradient so half-stars work without images. */
/* No JavaScript — scroll-snap rail; star ratings are a masked gradient so half-stars work without images. */Here's a working CSS Image Carousel from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Testimonial Card Carousel
Source: https://codefronts.com/snippets/css-image-carousel/testimonial-card-carousel/
A swipeable rail of review cards — each with a star rating, quote and avatar — snapping neatly one per view on mobile and two-up on wider screens. The social-proof carousel for landing pages, built on scroll-snap with no library.
## HTML
```html
<section class="car-06" aria-label="Customer testimonials">
<ul class="car-06__rail" tabindex="0" role="group" aria-label="Customer reviews, scrollable">
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" alt=""><span><b>Priya N.</b><small>Front-end Lead, UK</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 4.5 out of 5"><span class="car-06__fill" style="width:90%"></span></div>
<p class="car-06__quote">"Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" alt=""><span><b>Marcus T.</b><small>Web Dev, US</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" alt=""><span><b>Alira K.</b><small>UX Engineer, AU</small></span></div>
</li>
</ul>
</section>
```
## CSS
```css
.car-06,
.car-06 *,
.car-06 *::before,
.car-06 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-06 ::selection {
background: oklch(0.62 0.15 285);
color: #fff;
}
.car-06 {
--accent: oklch(0.62 0.15 285);
--star: #f5a623;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 12px;
background: #f5f4f9;
display: flex;
align-items: center;
justify-content: center;
}
.car-06__rail {
list-style: none;
display: flex;
gap: 18px;
width: min(760px,100%);
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding: 10px;
padding: 10px;
scrollbar-width: thin;
scrollbar-color: #d5d2e2 transparent;
}
.car-06__rail:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
border-radius: 16px;
}
.car-06__card {
flex: 0 0 clamp(240px,82%,320px);
scroll-snap-align: center;
background: #fff;
border: 1px solid #e9e7f1;
border-radius: 18px;
padding: 24px 22px;
box-shadow: 0 16px 34px -28px rgba(40,30,80,.7);
transition: transform .25s;
}
.car-06__card:hover {
transform: translateY(-4px);
}
.car-06__stars {
position: relative;
width: 112px;
height: 18px;
-webkit-mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
background: #e4e2ee;
}
.car-06__fill {
position: absolute;
inset: 0;
background: var(--star);
display: block;
}
.car-06__quote {
margin: 16px 0 20px;
font-size: 15px;
line-height: 1.55;
color: #33303d;
text-wrap: pretty;
}
.car-06__who {
display: flex;
align-items: center;
gap: 12px;
}
.car-06__ava {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
flex: none;
background: #e4e2ee;
}
.car-06__who b {
display: block;
font-size: 14px;
color: #26232f;
}
.car-06__who small {
display: block;
font-size: 12px;
color: #8b8797;
margin-top: 1px;
}
@media (min-width: 600px) {
.car-06__card {
flex-basis: calc((100% - 18px)/2);
}
}
@media (prefers-reduced-motion: reduce) {
.car-06__card {
transition: none;
}
.car-06__rail {
scroll-behavior: auto;
}
}
```
## JavaScript
```js
/* No JavaScript — scroll-snap rail; star ratings are a masked gradient so half-stars work without images. */
```Here's a working CSS Image Carousel from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Testimonial Card Carousel
Source: https://codefronts.com/snippets/css-image-carousel/testimonial-card-carousel/
A swipeable rail of review cards — each with a star rating, quote and avatar — snapping neatly one per view on mobile and two-up on wider screens. The social-proof carousel for landing pages, built on scroll-snap with no library.
## HTML
```html
<section class="car-06" aria-label="Customer testimonials">
<ul class="car-06__rail" tabindex="0" role="group" aria-label="Customer reviews, scrollable">
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" alt=""><span><b>Priya N.</b><small>Front-end Lead, UK</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 4.5 out of 5"><span class="car-06__fill" style="width:90%"></span></div>
<p class="car-06__quote">"Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" alt=""><span><b>Marcus T.</b><small>Web Dev, US</small></span></div>
</li>
<li class="car-06__card">
<div class="car-06__stars" role="img" aria-label="Rated 5 out of 5"><span class="car-06__fill" style="width:100%"></span></div>
<p class="car-06__quote">"Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work."</p>
<div class="car-06__who"><img class="car-06__ava" src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" alt=""><span><b>Alira K.</b><small>UX Engineer, AU</small></span></div>
</li>
</ul>
</section>
```
## CSS
```css
.car-06,
.car-06 *,
.car-06 *::before,
.car-06 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.car-06 ::selection {
background: oklch(0.62 0.15 285);
color: #fff;
}
.car-06 {
--accent: oklch(0.62 0.15 285);
--star: #f5a623;
font-family: 'Segoe UI',system-ui,sans-serif;
width: 100%;
min-height: 100vh;
padding: 40px 12px;
background: #f5f4f9;
display: flex;
align-items: center;
justify-content: center;
}
.car-06__rail {
list-style: none;
display: flex;
gap: 18px;
width: min(760px,100%);
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-padding: 10px;
padding: 10px;
scrollbar-width: thin;
scrollbar-color: #d5d2e2 transparent;
}
.car-06__rail:focus-visible {
outline: 3px solid var(--accent);
outline-offset: 2px;
border-radius: 16px;
}
.car-06__card {
flex: 0 0 clamp(240px,82%,320px);
scroll-snap-align: center;
background: #fff;
border: 1px solid #e9e7f1;
border-radius: 18px;
padding: 24px 22px;
box-shadow: 0 16px 34px -28px rgba(40,30,80,.7);
transition: transform .25s;
}
.car-06__card:hover {
transform: translateY(-4px);
}
.car-06__stars {
position: relative;
width: 112px;
height: 18px;
-webkit-mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
mask: repeat-x left/22.4px 18px url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2l3 6.3 6.9.9-5 4.8 1.2 6.9L12 17.7 5.9 20.9 7.1 14 2.1 9.2 9 8.3z'/%3E%3C/svg%3E");
background: #e4e2ee;
}
.car-06__fill {
position: absolute;
inset: 0;
background: var(--star);
display: block;
}
.car-06__quote {
margin: 16px 0 20px;
font-size: 15px;
line-height: 1.55;
color: #33303d;
text-wrap: pretty;
}
.car-06__who {
display: flex;
align-items: center;
gap: 12px;
}
.car-06__ava {
width: 44px;
height: 44px;
border-radius: 50%;
object-fit: cover;
flex: none;
background: #e4e2ee;
}
.car-06__who b {
display: block;
font-size: 14px;
color: #26232f;
}
.car-06__who small {
display: block;
font-size: 12px;
color: #8b8797;
margin-top: 1px;
}
@media (min-width: 600px) {
.car-06__card {
flex-basis: calc((100% - 18px)/2);
}
}
@media (prefers-reduced-motion: reduce) {
.car-06__card {
transition: none;
}
.car-06__rail {
scroll-behavior: auto;
}
}
```
## JavaScript
```js
/* No JavaScript — scroll-snap rail; star ratings are a masked gradient so half-stars work without images. */
```How this works
The rail is an overflow-x:auto flex row with scroll-snap-type:x mandatory; each testimonial card is a snap target (scroll-snap-align:center) sized with flex-basis. Touch users flick, desktop users scroll or tab into the focusable rail and use arrow keys — no JS.
Star ratings are drawn with a masked gradient so a partial rating (e.g. 4.5) is possible without images, and each rating exposes an accessible text label. Cards lift slightly on hover via transform only, keeping motion off the layout thread.
Make it yours
- Show one, two or three cards at once by editing the card
flex-basisand the breakpoint. - Recolour stars and accents through the
--accentproperty. - Swap centred snapping for start-aligned by changing
scroll-snap-align. - Drop the avatar to a monogram circle by removing the
<img>and styling the fallback.
Gotchas — read before shipping
- Expose the numeric rating as text (aria-label), not stars alone, so screen readers convey it.
- Reserve the avatar box with fixed width/height to avoid a late-load shift.
- Use
scroll-snap-align:centeronly if the card is narrower than the rail, or centred snapping feels off.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 69+.
scroll-snap + CSS mask; -webkit-mask included for older Safari.