Pure TailwindMIT licensed

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.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

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">&ldquo;Swapped our slider library for this and shaved 40&nbsp;KB off the bundle. Scroll feel is better than what we replaced.&rdquo;</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">&ldquo;Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes.&rdquo;</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">&ldquo;Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work.&rdquo;</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;
  }
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@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;
  }
}
3Fonts used by this demooptional — skip if you have your own type stack
<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 #f5a623 to oklch(0.72 0.15 75) and the byline grey darkened from #8b8797 to oklch(0.55 0.02 290). The original byline sat at 3.4:1 on white, below the 4.5:1 floor for 12px text.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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">&ldquo;Swapped our slider library for this and shaved 40&nbsp;KB off the bundle. Scroll feel is better than what we replaced.&rdquo;</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">&ldquo;Perfect CLS score after switching to the aspect-ratio pattern here. Copy-paste took ten minutes.&rdquo;</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">&ldquo;Finally a carousel my keyboard users can actually operate. Focus ring and arrow keys just work.&rdquo;</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-center for snap-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-11 and real width/height attributes; a late-loading 44px circle still shifts the quote.
  • snap-center on a card as wide as the rail feels wrong at the ends — use it only when the card is narrower.

Browser support

ChromeSafariFirefoxEdge
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.

Techniques used in this demo

Utilitiesfirst-class utility or variant

HTML & JSno Tailwind involved

Search CodeFronts

Loading…