testymo.

Free tools  ★  Tailwind testimonials

Tailwind testimonial card, section and slider

Three components as utility classes and nothing else — no stylesheet, no config, no plugin. Every preview below was drawn by Tailwind 4.3 from the exact classes printed under it, so what you copy is what you saw.

A Tailwind testimonial component is a customer quote styled entirely with utility classes in the markup, with no CSS file of its own. This page hands you three of them — a card, a responsive section and a scroll-snap slider — for developers already running Tailwind who would rather understand the classes than paste a block from a component gallery.

What it costs
Nothing. No account, no email, no watermark, no limit on how often you use it.
Where your text goes
Nowhere. The whole tool runs in your browser — nothing you type is sent, stored, or seen by us. The page-view measurement this site carries counts the visit, never the text.

Live previews — each one compiled from the classes shown beneath it

Sample quotes written for the demo — no customer wrote them.

1 — the card

0.7 KB of markup · 9.3 KB of compiled CSS

<figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 max-w-prose">
  <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
  <blockquote class="mt-3 text-slate-700">They rebuilt our checkout in a week and conversions doubled. I keep waiting for the catch.</blockquote>
  <figcaption class="mt-5 flex items-center gap-3">
    <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">JP</span>
    <span>
      <span class="block font-semibold text-slate-900">June Park</span>
      <span class="block text-sm text-slate-500">COO, Fernwell</span>
    </span>
  </figcaption>
</figure>

2 — the section, reflowing with no breakpoints

4.3 KB of markup · 9.4 KB of compiled CSS

<div class="grid gap-5 grid-cols-[repeat(auto-fit,minmax(min(15rem,100%),1fr))]">
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">They rebuilt our checkout in a week and conversions doubled. I keep waiting for the catch.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">JP</span>
      <span>
        <span class="block font-semibold text-slate-900">June Park</span>
        <span class="block text-sm text-slate-500">COO, Fernwell</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">Quietly the best agency we have worked with — no theatrics, no re-scoping, just the thing we asked for, early.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">AS</span>
      <span>
        <span class="block font-semibold text-slate-900">Amrit Sahani</span>
        <span class="block text-sm text-slate-500">Founder, Loam</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">They just get it.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">RD</span>
      <span>
        <span class="block font-semibold text-slate-900">Rafael Duarte</span>
        <span class="block text-sm text-slate-500">CTO, Solstice</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">The follow-up alone deserves five stars.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">NH</span>
      <span>
        <span class="block font-semibold text-slate-900">Nina Halvorsen</span>
        <span class="block text-sm text-slate-500">Operations, Kestrel Freight</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 4 out of 5 stars">★★★★☆</p>
    <blockquote class="mt-3 text-slate-700">Truly lovely people. The invoice, less so.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">IF</span>
      <span>
        <span class="block font-semibold text-slate-900">Idris Farah</span>
        <span class="block text-sm text-slate-500">Finance, Brindle</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">Nobody has ever complimented our checkout before. Two people did this month, unprompted, in the form.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">IF</span>
      <span>
        <span class="block font-semibold text-slate-900">Isabel Ferreira</span>
        <span class="block text-sm text-slate-500">Ecommerce, Two Rivers</span>
      </span>
    </figcaption>
  </figure>
</div>

3 — the slider, no carousel library

4.7 KB of markup · 9.9 KB of compiled CSS

<div class="flex snap-x snap-mandatory gap-5 overflow-x-auto scroll-pl-1.5 p-1.5 pb-5" tabindex="0" role="region" aria-label="Customer testimonials">
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 shrink-0 basis-[min(78vw,340px)] snap-start">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">They rebuilt our checkout in a week and conversions doubled. I keep waiting for the catch.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">JP</span>
      <span>
        <span class="block font-semibold text-slate-900">June Park</span>
        <span class="block text-sm text-slate-500">COO, Fernwell</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 shrink-0 basis-[min(78vw,340px)] snap-start">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">Signed for a month. Stayed three years. Make of that what you will.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">IS</span>
      <span>
        <span class="block font-semibold text-slate-900">Ivy Sandoval</span>
        <span class="block text-sm text-slate-500">CEO, Northbank</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 shrink-0 basis-[min(78vw,340px)] snap-start">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 3 out of 5 stars">★★★☆☆</p>
    <blockquote class="mt-3 text-slate-700">Good work overall, though the kickoff ran long and I would have liked the first draft a week earlier.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">RH</span>
      <span>
        <span class="block font-semibold text-slate-900">Reuben Hale</span>
        <span class="block text-sm text-slate-500">Product Manager, Cobbler</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 shrink-0 basis-[min(78vw,340px)] snap-start">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">I have already sent three friends. That is not a review, that is a confession.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">BL</span>
      <span>
        <span class="block font-semibold text-slate-900">Bea Lindqvist</span>
        <span class="block text-sm text-slate-500">Illustrator, Freelance</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 shrink-0 basis-[min(78vw,340px)] snap-start">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">Nobody has ever complimented our checkout before. Two people did this month, unprompted, in the form.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">IF</span>
      <span>
        <span class="block font-semibold text-slate-900">Isabel Ferreira</span>
        <span class="block text-sm text-slate-500">Ecommerce, Two Rivers</span>
      </span>
    </figcaption>
  </figure>
  <figure class="rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5 shrink-0 basis-[min(78vw,340px)] snap-start">
    <p class="text-amber-500 tracking-widest" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
    <blockquote class="mt-3 text-slate-700">Worth every penny, twice over.</blockquote>
    <figcaption class="mt-5 flex items-center gap-3">
      <span class="grid size-10 shrink-0 place-items-center rounded-full bg-indigo-600 text-sm font-bold text-white" aria-hidden="true">LN</span>
      <span>
        <span class="block font-semibold text-slate-900">Lena Novak</span>
        <span class="block text-sm text-slate-500">Owner, Studio Novak</span>
      </span>
    </figcaption>
  </figure>
</div>

01 Tailwind's grid scale has no auto-fit

Open any Tailwind testimonials block on the web and the section will be sm:grid-cols-2 lg:grid-cols-3. That is not a style choice, it is the scale running out: every grid-cols-n utility is repeat(n, minmax(0, 1fr)) with a fixed count, and there is no auto-fit anywhere in it. So the only way to get a second column is to name a breakpoint.

Breakpoints answer the wrong question. They measure the viewport, and what decides how many testimonial cards fit is the container — so the same section dropped into a sidebar on a 27-inch monitor still draws three columns, now squeezed into the sidebar's width, and looks broken. Two classes fix it, and which one you want depends on the Tailwind you are on.

The one used in the preview above works on v3 and v4 alike:

<div class="grid gap-5 grid-cols-[repeat(auto-fit,minmax(min(15rem,100%),1fr))]">

That is one arbitrary value carrying the whole responsive behaviour, and there is not a breakpoint in the component. The plain-CSS section page reads the same repeat(auto-fit, …) line apart, including why the inner min() is there.

The other answer is container queries, which v4 brought into core. Mark the container, then size against it:

<section class="@container">
  <div class="grid gap-5 @md:grid-cols-2 @3xl:grid-cols-3">
    ...
  </div>
</section>

@md: fires when the container passes 28rem, not the window — so the same section is one column in a sidebar and three across a full-width page, with the breakpoint names you already think in. On v3 this needs @tailwindcss/container-queries installed and registered; on v4 it is already there.

02 The slider, and the class everybody leaves out

The track and the card, in full:

<div class="flex snap-x snap-mandatory gap-5 overflow-x-auto scroll-pl-1.5 p-1.5 pb-5">
  <figure class="shrink-0 basis-[min(78vw,340px)] snap-start rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">

snap-x and snap-mandatory are two utilities rather than one because Tailwind splits scroll-snap-type across an axis class and a strictness class — snap-x alone sets the axis and leaves the strictness variable unset, which is proximity and feels like nothing is happening. You need both. snap-start then goes on each card.

shrink-0 is the one people leave out, and it is what breaks the slider. A flex item shrinks below its basis by default, so six cards in a flex row will each squash to a sixth of the track and nothing ever overflows — no overflow, no scrolling, no snapping. basis-* sets the preferred width; shrink-0 is what makes it the actual width. If your Tailwind slider renders as a row of slivers, that is the missing class.

scroll-pl-1.5 matches the track's own p-1.5 so a snapped card stops just inside the edge rather than flush against it, and the extra pb-5 gives the card shadows room inside a container that clips its overflow. Tailwind has the whole scroll-padding family — scroll-p-*, scroll-pl-*, scroll-pt-* — and they are on the spacing scale, so they line up with the padding classes by construction.

What this does and does not buy you against Swiper or Slick is the same accounting whatever the class names are, and the plain-CSS slider page works it through: arrows, dots, autoplay and an infinite loop are the things a library has that scroll-snap does not.

03 Written for v4. What changes if you are on v3.

Every class on this page is Tailwind v4 — 4.3 specifically, which is what compiled the previews. Most of it is identical on v3, but the handful that moved are exactly the ones a card uses, and they fail quietly: the component renders, it just renders wrong. If you paste a testimonial block written before v4 into a v4 project, or the reverse, check these four.

  • Shadows and radii shifted one step down. v3's shadow-sm is v4's shadow-xs, v3's bare shadow is v4's shadow-sm, and the same shuffle happened to rounded and blur. A v3 card pasted into v4 gets a shadow one size heavier than its author drew.
  • border no longer defaults to a colour. In v3 a bare border painted gray-200; in v4 it is currentColor, so a card outlined with border and no colour class suddenly wears a border in the text colour. Name the colour — border border-slate-200 — and it is right on both.
  • ring went from 3px to 1px. The card above uses ring-1, which is 1px in either version, so it is safe. A copied ring is not: write ring-3 on v4 to get the v3 look.
  • Opacity suffixes are gone. bg-opacity-50 and its text-, border- and ring- siblings were removed in v4 in favour of the slash — ring-slate-900/5, which the card uses. The slash works on v3 too, so it is the spelling to standardise on.

The configuration moved as well, and that part is loud rather than quiet: v4 has no tailwind.config.js by default. @tailwind base; @tailwind components; @tailwind utilities; becomes one @import "tailwindcss";, and theme values are CSS custom properties in an @theme block rather than JavaScript. Nothing on this page needs either, which is the point of keeping to the stock scale.

04 When the scale runs out: arbitrary values

Two classes in these components are in square brackets, and both are there because the scale genuinely has no answer — which is the only good reason to reach for one.

  • basis-[min(78vw,340px)] on the slider card. The basis-* scale is spacing steps, fractions and a set of fixed widths (basis-sm and friends). Not one of them is a min() and not one is a viewport unit, and the card wants both: 340px on a desktop, 78% of the screen on a phone, so a sliver of the next card shows and a thumb knows to swipe.
  • columns-[3_15rem] for the masonry column below. Tailwind has columns-3 (a count) and columns-sm (a width), but columns takes both at once and no utility pairs them.

An arbitrary value may not contain a space, because a space ends the class name. Use an underscore and Tailwind converts it back — which is why the masonry class is columns-[3_15rem] and not columns-[3 15rem], and why the grid track above is written with no spaces after its commas. Where a space would be invalid anyway, such as inside a url(), the underscore is left alone; everywhere else a backslash forces a literal one.

Check the scale before you bracket. The card's measure is max-w-prose, which is 65ch and lands in the same 60-to-75-character band a hand-written testimonial card aims for — so there is no max-w-[34em] here even though the plain-CSS version of this card uses exactly that. A bracket that duplicates a scale value is how a codebase ends up with four near-identical card widths.

05 Uneven quotes: clamp them, or stop the rows

A grid row is as tall as the longest quote in it, and real testimonials are not the same length. Tailwind gives you two answers to that and they are genuinely different decisions, not two spellings of one.

Clamp, and you pick the ceiling instead of your wordiest customer. Grid already makes the cards within a row equal height; what line-clamp-n changes is how tall that row is allowed to get. It has been core since v3.3 — before that it was the @tailwindcss/line-clamp plugin, which is why older Tailwind answers still tell you to install one.

<blockquote class="mt-3 text-slate-700 line-clamp-2">

Two lines here, which is deliberately brutal: in a column this narrow nothing gentler would visibly cut, and the cut is the part worth looking at.

Sample quotes written for the demo — no customer wrote them.

That is what truncation does to a sentence somebody wrote for you, and the clause that earned the quote its place is usually the last one. There is no “show more” either — line-clamp hides the rest with no way to reach it. On a real page clamp at four or five lines rather than two, and read what it does to your longest testimonial before you ship it.

Or drop the rows entirely. columns-[3_15rem] is CSS multi-column, so each card is exactly as tall as its own quote and nothing is cut:

<div class="columns-[3_15rem] gap-5">
  <figure class="mb-5 break-inside-avoid rounded-xl bg-white p-6 shadow-sm ring-1 ring-slate-900/5">

Sample quotes written for the demo — no customer wrote them.

break-inside-avoid is what stops a card being sliced at the foot of a column, and mb-5 replaces the gap, because gap-5 on a multi-column element only sets the column gap. The trade-off is reading order, and it is identical to the plain-CSS version of this layout — the section page spells it out: the eye goes down column one and back up, the document order is untouched, and which quote sits where changes with the width.

06 Dark mode is one prefix and one line

This is the thing utility classes are unambiguously better at. A second card design for a dark page is, in Tailwind, the same card with each colour restated once:

<figure class="rounded-xl bg-white ring-1 ring-slate-900/5
                dark:bg-slate-900 dark:ring-white/10">
  <blockquote class="text-slate-700 dark:text-slate-300">

Sample quotes written for the demo — no customer wrote them.

Out of the box dark: compiles to @media (prefers-color-scheme: dark) and follows the visitor's operating system. If your site has its own toggle, you override the variant in CSS — and this is a v4 change worth knowing, because the v3 answer was darkMode: "class" in a config file that no longer exists:

@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

Then a dark class anywhere above the card switches it, which is exactly what the preview above does — that card is not reading your OS setting, it is sitting inside a <div class="dark">. Swap the selector for &:where([data-theme=dark], [data-theme=dark] *) if you drive themes off a data attribute instead.

One thing to actually check rather than assume: the stars stay text-amber-500 in both modes here because amber holds up on white and on slate-900. Most accent colours do not survive the flip, and a rating row that is unreadable in dark mode is worse than no rating row.

07 Where the markup goes, and what Tailwind does not change

These blocks are markup, so they go wherever your markup lives — class in Astro, Vue, Svelte and plain HTML, className in React. The moment you map over an array of testimonials, though, there is one trap that catches everybody exactly once:

// Nothing. Tailwind scans your source as TEXT — it never
// sees this string, so the rule is never generated.
<p className={`text-${tone}-500`}>★★★★★</p>

// Write the whole class name out, every time.
const TONE = { amber: "text-amber-500", sky: "text-sky-500" };
<p className={TONE[tone]}>★★★★★</p>

Tailwind never runs your code. It scans your source files as plain text looking for things that look like class names, so a class assembled at runtime does not exist as far as the compiler is concerned and the rule is never in the stylesheet. It works in dev if some other file happens to mention the same class, and then disappears in production — the worst shape a bug can have. Map to complete class strings instead.

And the one thing this page cannot do that its three siblings can. There is no <style> in any block above, so pasting one into a Squarespace, Wix or WordPress custom-HTML box gets you unstyled text. It needs a Tailwind build. If you are looking for a block to paste into a site builder, you want the plain-CSS card, section or slider instead, or the generator to set the colours and quotes first. That is a real fork in the road, not a preference.

Three things Tailwind changes nothing about, so they are worth reading once on the page that owns them rather than four times here. The anatomy — <figure>, <blockquote>, <figcaption>, and why the name must not go in a <cite> — is on the card page, and this page's markup is that markup. The tabindex, role and aria-label a scroll track needs are on the slider page. And the layout-shift story is the same too: no images, initials instead of headshots, and your own webfont swap as the only remaining risk.

The last thing hand-written markup costs you arrives with the next testimonial, and it is the same whichever way you styled it: a new quote is a code change and a deploy, and the section quietly stops being maintained the month things get busy. That is the line where a live widget earns its script tag. Where any of these belong on a page is worked through in social proof sections.

08 Questions this page gets

Do I need a plugin or a config file for any of this?
No. Every class here is core in Tailwind v4 with a stock install and no configuration. On v3 the only extras are @tailwindcss/container-queries if you take the @container route in section 01, and @tailwindcss/line-clamp if you are on a version before 3.3.
Should I pull these into @apply and a .testimonial-card class?
Usually not. Tailwind's own guidance is that a component is the unit of reuse, not a CSS class, so if you are repeating a card, extract the markup — a React or Astro component taking a quote and a name — rather than the class list. @apply earns its place where the documentation puts it: writing custom CSS that has to override a third-party library’s own classes, while still using your design tokens.
Why is the preview not just loading Tailwind from a CDN?
Two reasons, and the second is the honest one. This site's content security policy only allows scripts from its own origin, so the play CDN would be blocked outright. And it is a compiler: several hundred kilobytes of JavaScript, shipped to draw six cards. Each preview above instead carries the CSS Tailwind 4.3 generated at build time from that snippet's own classes, which is the figure printed beside each block.
Is a testimonial card in Tailwind heavier than one in plain CSS?
Slightly, for one card, and less as you add more: the utilities are shared across everything else on your page, while a hand-written block's CSS is only ever used by that block. The figures beside each preview are the honest comparison — they include Tailwind's preflight reset, which your site pays for once no matter how many components you have.
Can I change the colours?
Swap indigo-600 on the avatar and amber-500 on the stars for anything on your palette, or for a --color-* variable you defined in @theme. If you want to pick colours visually and get a finished block, the generator does that — though it emits HTML and CSS rather than utility classes.
Can I use these sample testimonials?
No — they are invented for the demo and nobody said them. Replace every one with words a real customer wrote and agreed you could publish. Permission to use testimonials covers what to ask for and what to keep on file.

More free tools