testymo.

Free tools  ★  Testimonial card HTML

Testimonial card in HTML and CSS

One quote, one card, on a measure built for reading it. Below are the markup and the CSS in five designs — and the attribution mistake that shows up in nearly every testimonial card on the web.

A testimonial card is a single customer quote set as a self-contained block: the words, who said them, and usually a star rating. This page hands you one as plain HTML and CSS in five designs, for freelancers, agencies and small business owners who want one strong quote beside a call to action rather than a wall of them.

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 preview — the card, at the measure it ships with

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

The whole card — HTML and CSS, one block

1.4 KB · no JavaScript

<!-- Testimonial section — generated free at testymo.com/free/testimonial-html-generator -->
<section class="tm-testimonials">
  <style>
  .tm-testimonials { font: inherit; }
  .tm-list { max-width: 34em; }
  .tm-card { margin: 0 0 20px; }
  .tm-card { background: #ffffff; border-radius: 8px; padding: 22px 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.09); }
  .tm-stars { margin: 0; color: #1233d4; letter-spacing: 3px; }
  .tm-quote { margin: 10px 0 16px; line-height: 1.55; }
  .tm-who { display: flex; align-items: center; gap: 10px; }
  .tm-avatar { display: grid; place-items: center; width: 38px; height: 38px; border-radius: 50%; background: #1233d4; color: #ffffff; font-size: 0.8em; font-weight: 700; }
  .tm-name { display: block; font-weight: 700; }
  .tm-role { display: block; font-size: 0.85em; opacity: 0.72; }
  </style>
  <div class="tm-list">
    <figure class="tm-card">
      <p class="tm-stars" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
      <blockquote class="tm-quote">They rebuilt our checkout in a week and conversions doubled. I keep waiting for the catch.</blockquote>
      <figcaption class="tm-who">
        <span class="tm-avatar" aria-hidden="true">JP</span>
        <span>
          <span class="tm-name">June Park</span>
          <span class="tm-role">COO, Fernwell</span>
        </span>
      </figcaption>
    </figure>
  </div>
</section>

01 The markup, and the attribution mistake to avoid

Strip the colours away and a testimonial card is four elements. This is the shape, and every part of it is load-bearing:

<figure class="tm-card">
  <p class="tm-stars" role="img" aria-label="Rated 5 out of 5 stars">★★★★★</p>
  <blockquote class="tm-quote">They rebuilt our checkout in a week.</blockquote>
  <figcaption class="tm-who">
    <span class="tm-avatar" aria-hidden="true">JP</span>
    <span>
      <span class="tm-name">June Park</span>
      <span class="tm-role">COO, Fernwell</span>
    </span>
  </figcaption>
</figure>

Here is the mistake. Search for testimonial card markup and you will find <cite>June Park</cite> under the quote, over and over. <cite> is for the title of a work — a book, a film, a paper, the page a quotation came from — and explicitly not for the name of a person. The element that exists for attributing a quotation is <figcaption> inside a <figure>, which is what this card uses. It costs nothing to get right and it is the difference between markup that describes a testimonial and markup that describes a citation of a book called June Park.

The rest follows from that. The words go in a real <blockquote>, so a screen reader announces them as a quotation rather than reading them as body copy. The stars are one element with role="img" and an aria-label, so the rating is heard as “Rated 5 out of 5 stars” and not as five identical characters. And the initials circle is aria-hidden, because the name it abbreviates is on the very next line and hearing “J P June Park” helps nobody.

02 Five card looks, and the exact CSS for each

Every look below is the same markup with one rule changed — the .tm-card rule. Copy the one you want over the rule in the block at the top of this page, or pick it in the generator and get the finished snippet.

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

Classic. The safe one. A white card and a one-pixel shadow sit on top of almost any page without arguing with it — start here and only move if it disappears into your background.

.tm-card { background: #ffffff; border-radius: 8px; padding: 22px 24px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.09); }

Soft studio. A 20px radius and a long, low shadow: the look of a modern SaaS marketing page. It needs a light, uncluttered background to read as soft rather than as blurry.

.tm-card { background: #ffffff; border: 1px solid rgba(16, 24, 40, 0.07); border-radius: 20px; padding: 24px 26px; box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 10px 28px -12px rgba(16, 24, 40, 0.14); }

Swiss rule. No card at all — a 2px rule above the quote and nothing else. The one to use when the quote sits inside an existing panel, because a card inside a card reads as a mistake.

.tm-card { border-top: 2px solid #111111; padding: 14px 0 0; }

Brut. A hard black border and an offset block shadow, with no radius. Loud on purpose, and the only look here that needs room around it — give it margin or it fights its neighbours.

.tm-card { background: #ffffff; border: 2px solid #111111; border-radius: 0px; padding: 20px 22px; box-shadow: 5px 5px 0 #111111; }

Obsidian. A dark card with a warm gold accent, for a dark page or a deliberately heavy pull quote. Check the contrast if you change the accent: the card sets its own text to #c2c2c9 on #131316, and a lighter accent will not survive it.

.tm-card { background: #131316; border: 1px solid #26262b; border-radius: 14px; padding: 24px 26px; box-shadow: 0 18px 40px -28px #000000; color: #c2c2c9; }

03 34em: the measure, and why it is not in pixels

The card itself is a box; the only layout rule on the whole pattern is how wide that box is allowed to get:

.tm-list { max-width: 34em; }
.tm-card { margin: 0 0 20px; }

34em of the inherited font is somewhere around 60 to 75 characters a line, which is the band where prose stops being tiring to read. Wider and the eye loses the start of the next line; much narrower and it breaks every sentence into three.

The unit is the point. An em here is measured in the font the card inherits, so if your site sets body text at 18px instead of 16px, the card grows with it and the line still holds about the same number of characters. A max-width in pixels does the opposite: bigger type in the same box means fewer words per line, and the card starts to read as cramped for exactly the readers who needed the larger text. There is no width, only max-width, so the card fills a phone and stops at a comfortable measure on a desktop — no media query involved.

04 When one card beats a wall of them

A wall of testimonials asks to be scanned. A single card asks to be read. Those are different jobs, and the card wins wherever you want one actual sentence to land in somebody’s head at the moment they are deciding: beside the price, next to the button, at the end of a long sales page, in a sidebar on a services page.

Three things follow from picking one quote instead of nine:

  • You get one objection, so choose it deliberately. The card cannot cover price, speed and support at once. Pick the quote that answers the doubt this particular page leaves a visitor with — which is rarely the most flattering one you have.
  • Length matters more here than in any other pattern. On a 320px phone the card’s own padding leaves you roughly 270px of measure, and an eighty-word quote becomes a grey brick nobody starts. Keep a single-card quote under about forty words; if it needs more, it wants a section where the reader has already agreed to browse.
  • A card next to a call to action needs no heading. A section wants “What our customers say” above it; a card sitting beside a button is self-evident, and a heading on it only makes it look like a section that lost the rest of its cards.

05 Initials instead of a headshot, and what that saves

The card is where people reach for a photo, so it is worth being clear about what a headshot actually costs. It is a request to a customer, a file to crop and host, a size to get right on two densities — and, if you ship it the usual way, a layout shift, because an <img> with no width and height reserves no space and pushes everything under it down the moment it arrives.

The circle in this card is the person’s initials on the accent colour, generated from the name. It loads with the HTML, cannot move the layout, needs no permission of its own and looks deliberate rather than like a missing image. For a card whose whole point is one quote read once, that is the right trade.

When a real photo is worth it: when the person is recognisable to your audience, or their face is the proof — a known founder, a local customer, a before-and-after. Then use one, get permission for the image as well as the words, and give the <img> explicit width and height attributes matching its real aspect ratio so it reserves its own box. The testimonial image generator is the other direction: the same quote drawn as a PNG for a social post.

06 Make it yours, or stop re-pasting it

The accent, card background, corner radius and the quote itself are all editable in the testimonial HTML generator, which emits this exact snippet from your own words. The two sibling patterns are a responsive testimonial section when you have a set of quotes to show together, and a scroll-snap slider when you have more of them than the page has room for, and the same three in Tailwind classes if your site is already built that way. For the words themselves — how to ask so you get a quote worth putting on a card — there is the question picker.

One card is the pattern that ages best as pasted HTML: it is a single quote you chose on purpose, and it is meant to stay put. The moment you find yourself editing the page to swap it, that is the signal you wanted a live widget instead — approve a new testimonial and the card changes at the next page load.

07 Questions this page gets

Should the name be in a <cite> tag?
No. <cite> marks the title of a work, not a person’s name — see the markup section. The attribution belongs in the <figcaption>, which is what this card does.
How do I put the card beside a button rather than above it?
Wrap the two in your own flex container: display: flex; gap: 32px; align-items: center; on the parent, and let the card’s max-width do the rest. The snippet sets no width, so it cooperates with whatever layout you drop it into.
Can I show the star rating without the stars?
Delete the <p class="tm-stars"> line and its rule. A quote with no rating is a perfectly good testimonial — and if you want the number rendered as stars somewhere else on the page, that is the star rating HTML generator.
Will Google show a star rating in search results for this?
Not from this markup, and that is deliberate: the card carries no Review or AggregateRating structured data. If you want to mark up testimonials you really collected, the review schema generator explains which page types Google actually shows stars for — a vendor rating itself is not one of them.
Does it work in a site builder or a CMS block?
Yes. The CSS travels inside the snippet and there is no JavaScript, so it pastes into any custom-HTML box — the integrations pages show where that box is on each platform — and into a CodePen HTML pane with the CSS pane left empty.
Can I use the sample quote?
No — it is invented for the demo and nobody said it. Replace it 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