06 / 22
All 22 in 22 Tailwind CSS Image Carousel
- 01Tailwind E-commerce Product Gallery Carousel with Thumbnails
- 02Tailwind Shop the Look Multi-Item Card Slider with Scroll Snap
- 03Tailwind Infinite Logo Scroller Marquee with No JavaScript
- 04Tailwind Product Colour Variant Picker Slider
- 05Tailwind Full-Screen Hero Slideshow with Auto Cross-Fade
- 06Tailwind Testimonial Card Carousel with Star Ratings
- 07Tailwind Meet the Team Profile Slider with Circular Portraits
- 08Tailwind Portfolio Gallery Slider with Hover Caption Reveal
- 09Tailwind Netflix-Style Content Row Carousel with Hover Expand
- 10Tailwind Magazine Split-Screen Article Slider
- 11Tailwind Onboarding Slider with Native CSS Scroll-Marker Dots
- 12Tailwind Vertical Image Carousel with Y-Axis Scroll Snap
- 13Tailwind 3D Coverflow Carousel with Perspective Transforms
- 14Tailwind Cross-Fade Image Slideshow with Manual Controls
- 15Tailwind Dual Opposite-Direction Marquee Slider
- 16Tailwind Dot Pagination Carousel with Target and Has Selectors
- 17Tailwind Prev / Next Arrow Carousel with Native Scroll Buttons
- 18Tailwind Before / After Image Comparison Slider
- 19Tailwind Accordion Expanding Image Panel Carousel
- 20Tailwind Autoplay Slider with Keyframe Track and Progress Bars
- 21Tailwind Zoom-on-Hover Image Gallery Slider
- 22Tailwind Parallax Text-Over-Image Story Slider
Tailwind Testimonial Card Carousel with Star Ratings
A swipeable rail of review cards — star rating, quote, avatar — snapping one per view on mobile and two-up wide. The half-star rating is a masked bar rather than five glyphs, which means a 4.5 needs no extra image and no icon font.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="tcar-06 w-full min-h-svh grid place-items-center bg-tcar06-bg font-tcar06-sans px-3 py-10" aria-label="Customer testimonials">
<ul tabindex="0" role="group" aria-label="Customer reviews, scrollable" class="list-none flex gap-[18px] w-[min(760px,100%)] overflow-x-auto p-2.5 m-0 snap-x snap-mandatory [scroll-padding:10px] [scrollbar-width:thin] [scrollbar-color:var(--color-tcar06-track)_transparent] focus-visible:outline-3 focus-visible:outline-tcar06-acc focus-visible:outline-offset-2 focus-visible:rounded-2xl">
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Priya N.</b><small class="block text-xs text-tcar06-mute mt-px">Front-end Lead, UK</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 4.5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:90%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Marcus T.</b><small class="block text-xs text-tcar06-mute mt-px">Web Dev, US</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Alira K.</b><small class="block text-xs text-tcar06-mute mt-px">UX Engineer, AU</small></span></div>
</li>
</ul>
</section><section class="tcar-06 w-full min-h-svh grid place-items-center bg-tcar06-bg font-tcar06-sans px-3 py-10" aria-label="Customer testimonials">
<ul tabindex="0" role="group" aria-label="Customer reviews, scrollable" class="list-none flex gap-[18px] w-[min(760px,100%)] overflow-x-auto p-2.5 m-0 snap-x snap-mandatory [scroll-padding:10px] [scrollbar-width:thin] [scrollbar-color:var(--color-tcar06-track)_transparent] focus-visible:outline-3 focus-visible:outline-tcar06-acc focus-visible:outline-offset-2 focus-visible:rounded-2xl">
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Priya N.</b><small class="block text-xs text-tcar06-mute mt-px">Front-end Lead, UK</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 4.5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:90%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Marcus T.</b><small class="block text-xs text-tcar06-mute mt-px">Web Dev, US</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Alira K.</b><small class="block text-xs text-tcar06-mute mt-px">UX Engineer, AU</small></span></div>
</li>
</ul>
</section>@import "tailwindcss";
@theme {
--color-tcar06-bg: #f5f4f9;
--color-tcar06-bg: oklch(0.96 0.006 290);
--color-tcar06-edge: #e9e7f1;
--color-tcar06-edge: oklch(0.92 0.01 290);
--color-tcar06-track: #d5d2e2;
--color-tcar06-track: oklch(0.86 0.015 290);
--color-tcar06-startrack: #e4e2ee;
--color-tcar06-startrack: oklch(0.91 0.012 290);
--color-tcar06-star: #d98b00;
--color-tcar06-star: oklch(0.72 0.15 75);
--color-tcar06-ink: #33303d;
--color-tcar06-ink: oklch(0.32 0.015 290);
--color-tcar06-mute: #6f6a80;
--color-tcar06-mute: oklch(0.55 0.02 290);
--color-tcar06-acc: #6d54c7;
--color-tcar06-acc: oklch(0.52 0.16 290);
--font-tcar06-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-06 {
width: 100%;
min-height: 100svh;
}
.tcar-06 ::selection {
background: var(--color-tcar06-acc);
color: #fff;
}
/* The star mask: a data-URI repeat, too long and too structural to live in a class list. */
.tcar06-stars {
-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");
}
@media (prefers-reduced-motion: reduce) {
.tcar-06 ul {
scroll-behavior: auto;
}
}@import "tailwindcss";
@theme {
--color-tcar06-bg: #f5f4f9;
--color-tcar06-bg: oklch(0.96 0.006 290);
--color-tcar06-edge: #e9e7f1;
--color-tcar06-edge: oklch(0.92 0.01 290);
--color-tcar06-track: #d5d2e2;
--color-tcar06-track: oklch(0.86 0.015 290);
--color-tcar06-startrack: #e4e2ee;
--color-tcar06-startrack: oklch(0.91 0.012 290);
--color-tcar06-star: #d98b00;
--color-tcar06-star: oklch(0.72 0.15 75);
--color-tcar06-ink: #33303d;
--color-tcar06-ink: oklch(0.32 0.015 290);
--color-tcar06-mute: #6f6a80;
--color-tcar06-mute: oklch(0.55 0.02 290);
--color-tcar06-acc: #6d54c7;
--color-tcar06-acc: oklch(0.52 0.16 290);
--font-tcar06-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-06 {
width: 100%;
min-height: 100svh;
}
.tcar-06 ::selection {
background: var(--color-tcar06-acc);
color: #fff;
}
/* The star mask: a data-URI repeat, too long and too structural to live in a class list. */
.tcar06-stars {
-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");
}
@media (prefers-reduced-motion: reduce) {
.tcar-06 ul {
scroll-behavior: auto;
}
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-tcar06-bg: #f5f4f9;
--color-tcar06-bg: oklch(0.96 0.006 290);
--color-tcar06-edge: #e9e7f1;
--color-tcar06-edge: oklch(0.92 0.01 290);
--color-tcar06-track: #d5d2e2;
--color-tcar06-track: oklch(0.86 0.015 290);
--color-tcar06-startrack: #e4e2ee;
--color-tcar06-startrack: oklch(0.91 0.012 290);
--color-tcar06-star: #d98b00;
--color-tcar06-star: oklch(0.72 0.15 75);
--color-tcar06-ink: #33303d;
--color-tcar06-ink: oklch(0.32 0.015 290);
--color-tcar06-mute: #6f6a80;
--color-tcar06-mute: oklch(0.55 0.02 290);
--color-tcar06-acc: #6d54c7;
--color-tcar06-acc: oklch(0.52 0.16 290);
--font-tcar06-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-06 {
width: 100%;
min-height: 100svh;
}
.tcar-06 ::selection {
background: var(--color-tcar06-acc);
color: #fff;
}
/* The star mask: a data-URI repeat, too long and too structural to live in a class list. */
.tcar06-stars {
-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");
}
@media (prefers-reduced-motion: reduce) {
.tcar-06 ul {
scroll-behavior: auto;
}
}@import "tailwindcss";
@theme {
--color-tcar06-bg: #f5f4f9;
--color-tcar06-bg: oklch(0.96 0.006 290);
--color-tcar06-edge: #e9e7f1;
--color-tcar06-edge: oklch(0.92 0.01 290);
--color-tcar06-track: #d5d2e2;
--color-tcar06-track: oklch(0.86 0.015 290);
--color-tcar06-startrack: #e4e2ee;
--color-tcar06-startrack: oklch(0.91 0.012 290);
--color-tcar06-star: #d98b00;
--color-tcar06-star: oklch(0.72 0.15 75);
--color-tcar06-ink: #33303d;
--color-tcar06-ink: oklch(0.32 0.015 290);
--color-tcar06-mute: #6f6a80;
--color-tcar06-mute: oklch(0.55 0.02 290);
--color-tcar06-acc: #6d54c7;
--color-tcar06-acc: oklch(0.52 0.16 290);
--font-tcar06-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-06 {
width: 100%;
min-height: 100svh;
}
.tcar-06 ::selection {
background: var(--color-tcar06-acc);
color: #fff;
}
/* The star mask: a data-URI repeat, too long and too structural to live in a class list. */
.tcar06-stars {
-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");
}
@media (prefers-reduced-motion: reduce) {
.tcar-06 ul {
scroll-behavior: auto;
}
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The star mask is the one thing in this demo that should NOT be a utility. A 300-character data URI in a class attribute is unreadable, unshareable between the three cards, and would be repeated verbatim three times; a one-line CSS class is the right call and Tailwind's own docs agree.
- The rating fill width stays an inline
style. It is per-review data, and a utility would imply the design system knows what 4.5 stars looks like. - UI change from the CSS original: the star colour moved from
#f5a623tooklch(0.72 0.15 75)and the byline grey darkened from#8b8797tooklch(0.55 0.02 290). The original byline sat at 3.4:1 on white, below the 4.5:1 floor for 12px text.
Here's a working CSS demo 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: Tailwind Testimonial Card Carousel with Star Ratings
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-testimonial-card-carousel-star-ratings/
A swipeable rail of review cards — star rating, quote, avatar — snapping one per view on mobile and two-up wide. The half-star rating is a masked bar rather than five glyphs, which means a 4.5 needs no extra image and no icon font.
## HTML
```html
<section class="tcar-06 w-full min-h-svh grid place-items-center bg-tcar06-bg font-tcar06-sans px-3 py-10" aria-label="Customer testimonials">
<ul tabindex="0" role="group" aria-label="Customer reviews, scrollable" class="list-none flex gap-[18px] w-[min(760px,100%)] overflow-x-auto p-2.5 m-0 snap-x snap-mandatory [scroll-padding:10px] [scrollbar-width:thin] [scrollbar-color:var(--color-tcar06-track)_transparent] focus-visible:outline-3 focus-visible:outline-tcar06-acc focus-visible:outline-offset-2 focus-visible:rounded-2xl">
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Priya N.</b><small class="block text-xs text-tcar06-mute mt-px">Front-end Lead, UK</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 4.5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:90%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Marcus T.</b><small class="block text-xs text-tcar06-mute mt-px">Web Dev, US</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Alira K.</b><small class="block text-xs text-tcar06-mute mt-px">UX Engineer, AU</small></span></div>
</li>
</ul>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcar06-bg: #f5f4f9;
--color-tcar06-bg: oklch(0.96 0.006 290);
--color-tcar06-edge: #e9e7f1;
--color-tcar06-edge: oklch(0.92 0.01 290);
--color-tcar06-track: #d5d2e2;
--color-tcar06-track: oklch(0.86 0.015 290);
--color-tcar06-startrack: #e4e2ee;
--color-tcar06-startrack: oklch(0.91 0.012 290);
--color-tcar06-star: #d98b00;
--color-tcar06-star: oklch(0.72 0.15 75);
--color-tcar06-ink: #33303d;
--color-tcar06-ink: oklch(0.32 0.015 290);
--color-tcar06-mute: #6f6a80;
--color-tcar06-mute: oklch(0.55 0.02 290);
--color-tcar06-acc: #6d54c7;
--color-tcar06-acc: oklch(0.52 0.16 290);
--font-tcar06-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-06 {
width: 100%;
min-height: 100svh;
}
.tcar-06 ::selection {
background: var(--color-tcar06-acc);
color: #fff;
}
/* The star mask: a data-URI repeat, too long and too structural to live in a class list. */
.tcar06-stars {
-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");
}
@media (prefers-reduced-motion: reduce) {
.tcar-06 ul {
scroll-behavior: auto;
}
}
```Here's a working CSS demo 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: Tailwind Testimonial Card Carousel with Star Ratings
Source: https://codefronts.com/snippets/tailwind-css-image-carousel/tailwind-testimonial-card-carousel-star-ratings/
A swipeable rail of review cards — star rating, quote, avatar — snapping one per view on mobile and two-up wide. The half-star rating is a masked bar rather than five glyphs, which means a 4.5 needs no extra image and no icon font.
## HTML
```html
<section class="tcar-06 w-full min-h-svh grid place-items-center bg-tcar06-bg font-tcar06-sans px-3 py-10" aria-label="Customer testimonials">
<ul tabindex="0" role="group" aria-label="Customer reviews, scrollable" class="list-none flex gap-[18px] w-[min(760px,100%)] overflow-x-auto p-2.5 m-0 snap-x snap-mandatory [scroll-padding:10px] [scrollbar-width:thin] [scrollbar-color:var(--color-tcar06-track)_transparent] focus-visible:outline-3 focus-visible:outline-tcar06-acc focus-visible:outline-offset-2 focus-visible:rounded-2xl">
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Swapped our slider library for this and shaved 40 KB off the bundle. Scroll feel is better than what we replaced.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-1/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Priya N.</b><small class="block text-xs text-tcar06-mute mt-px">Front-end Lead, UK</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 4.5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:90%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-2/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Marcus T.</b><small class="block text-xs text-tcar06-mute mt-px">Web Dev, US</small></span></div>
</li>
<li class="flex-none basis-[clamp(240px,82%,320px)] sm:basis-[calc((100%-18px)/2)] snap-center bg-white border border-tcar06-edge rounded-[18px] px-[22px] py-6 shadow-[0_16px_34px_-28px_oklch(0.3_0.08_290/0.7)] transition-[translate] duration-250 hover:-translate-y-1 motion-reduce:transition-none motion-reduce:hover:translate-y-0">
<div role="img" aria-label="Rated 5 out of 5" class="relative w-28 h-[18px] bg-tcar06-startrack tcar06-stars"><span style="width:100%" class="absolute inset-y-0 left-0 block bg-tcar06-star"></span></div>
<p class="mt-4 mb-5 text-[15px] leading-[1.55] text-tcar06-ink text-pretty">“Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work.”</p>
<div class="flex items-center gap-3"><img src="https://picsum.photos/seed/ava-3/96/96" width="96" height="96" loading="lazy" decoding="async" alt="" class="size-11 rounded-full object-cover flex-none bg-tcar06-edge"><span><b class="block text-sm text-tcar06-ink">Alira K.</b><small class="block text-xs text-tcar06-mute mt-px">UX Engineer, AU</small></span></div>
</li>
</ul>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-tcar06-bg: #f5f4f9;
--color-tcar06-bg: oklch(0.96 0.006 290);
--color-tcar06-edge: #e9e7f1;
--color-tcar06-edge: oklch(0.92 0.01 290);
--color-tcar06-track: #d5d2e2;
--color-tcar06-track: oklch(0.86 0.015 290);
--color-tcar06-startrack: #e4e2ee;
--color-tcar06-startrack: oklch(0.91 0.012 290);
--color-tcar06-star: #d98b00;
--color-tcar06-star: oklch(0.72 0.15 75);
--color-tcar06-ink: #33303d;
--color-tcar06-ink: oklch(0.32 0.015 290);
--color-tcar06-mute: #6f6a80;
--color-tcar06-mute: oklch(0.55 0.02 290);
--color-tcar06-acc: #6d54c7;
--color-tcar06-acc: oklch(0.52 0.16 290);
--font-tcar06-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
}
.tcar-06 {
width: 100%;
min-height: 100svh;
}
.tcar-06 ::selection {
background: var(--color-tcar06-acc);
color: #fff;
}
/* The star mask: a data-URI repeat, too long and too structural to live in a class list. */
.tcar06-stars {
-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");
}
@media (prefers-reduced-motion: reduce) {
.tcar-06 ul {
scroll-behavior: auto;
}
}
```How this works
The rating is one element, not five. A repeating SVG star mask over a grey bar, with a coloured fill absolutely positioned inside at width:90%. Half and quarter ratings cost nothing, and there is no icon font to load or fail.
The fill percentage stays inline. style="width:90%" is data — it comes from the review, not the design system — so it does not belong in a class or a token.
Snapping centres the card. snap-center reads better than snap-start when the card is comfortably narrower than the rail, which is the case on mobile here.
Make it yours
- Cards per view: the
basis-*pair on the card. - Star colour:
--color-tcar06-star; accents and focus ring:--color-tcar06-acc. - Start-aligned snapping: swap
snap-centerforsnap-start. - Monogram avatars: drop the
<img>and style the circle.
Gotchas — read before shipping
- The numeric rating must exist as text (the
aria-label). A masked bar is invisible to assistive tech — it is decoration carrying meaning unless you label it. - Reserve the avatar box with
size-11and realwidth/heightattributes; a late-loading 44px circle still shifts the quote. snap-centeron a card as wide as the rail feels wrong at the ends — use it only when the card is narrower.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
scroll-snap is Baseline; mask needs Safari 15.4+ (the -webkit-mask shim is included). The floor quoted is Tailwind v4's generated sheet.