HTML Formatterhtmlformatteronline.com

Product card

An e-commerce card with image, price, rating and an add-to-basket action.

cardproductecommerceshopgrid
<div class="grid">

  <article class="card">
    <a class="media" href="/p/ellery-shirt/">
      <img src="https://placehold.co/600x750/e8ecf4/8593ab?text=Linen+shirt" alt="Oatmeal linen shirt, folded">
      <span class="badge">New</span>
    </a>
    <div class="body">
      <h3><a href="/p/ellery-shirt/">Ellery linen shirt</a></h3>
      <p class="sub">Oatmeal · Unisex</p>
      <div class="rating" aria-label="Rated 4.5 out of 5 from 128 reviews">
        <span class="stars" style="--pct:90%" aria-hidden="true">★★★★★</span>
        <span class="count">128</span>
      </div>
      <p class="price"><span class="now">£58</span> <s class="was">£72</s></p>
      <button class="buy" type="button">Add to basket</button>
    </div>
  </article>

  <article class="card">
    <a class="media" href="/p/wool-scarf/">
      <img src="https://placehold.co/600x750/e6efe9/7ba88f?text=Wool+scarf" alt="Green lambswool scarf">
    </a>
    <div class="body">
      <h3><a href="/p/wool-scarf/">Lambswool scarf</a></h3>
      <p class="sub">Moss · One size</p>
      <div class="rating" aria-label="Rated 5 out of 5 from 41 reviews">
        <span class="stars" style="--pct:100%" aria-hidden="true">★★★★★</span>
        <span class="count">41</span>
      </div>
      <p class="price"><span class="now">£34</span></p>
      <button class="buy" type="button">Add to basket</button>
    </div>
  </article>

  <article class="card">
    <a class="media" href="/p/canvas-tote/">
      <img src="https://placehold.co/600x750/f3ecdf/b79a6a?text=Canvas+tote" alt="Natural canvas tote bag">
      <span class="badge out">Out of stock</span>
    </a>
    <div class="body">
      <h3><a href="/p/canvas-tote/">Canvas tote</a></h3>
      <p class="sub">Natural · 14oz</p>
      <div class="rating" aria-label="Rated 4 out of 5 from 9 reviews">
        <span class="stars" style="--pct:80%" aria-hidden="true">★★★★★</span>
        <span class="count">9</span>
      </div>
      <p class="price"><span class="now">£26</span></p>
      <button class="buy" type="button" disabled>Notify me</button>
    </div>
  </article>

</div>

How it works

aspect-ratio holds the layout still. Setting aspect-ratio: 4 / 5 on the image means the browser reserves the right space before the image arrives, so the grid does not jump when it loads. That jump is measured as Cumulative Layout Shift and it is a Core Web Vitals metric.

The stars are one element. A gradient clipped to text, with a custom property for the fill percentage. Five separate icon elements per card, times thirty cards, is a lot of DOM for something decorative — and this version handles half stars for free by setting --pct: 90%.

The rating has a real label. The star glyphs are aria-hidden because "black star black star black star" is not useful. The aria-label on the container says what the rating actually is.

flex-direction: column with flex: 1 on the body is what keeps the buttons aligned across a row when product names wrap to different numbers of lines. Without it, a two-line title pushes its button down and the row looks broken.

The disabled state is a real disabled attribute, not a class. It removes the button from the tab order and announces itself, which a greyed-out but clickable button does not.

Accessibility notes

The image alt text describes the product, not the file. "Oatmeal linen shirt, folded" is useful; "IMG_4471" and "product image" are not.

Price is inside a normal paragraph so it is read in sequence. The struck-through original uses <s>, which most screen readers announce as deleted content — if the distinction matters commercially, add visually hidden text saying "was £72, now £58".

Do not rely on the badge colour alone to signal stock status. "Out of stock" is written out, and the button text changes as well.

Making it yours

Change minmax(230px, 1fr) to set how narrow a card may get before the grid drops a column. No media queries needed — the grid reflows on its own.

The hover lift animates transform rather than box-shadow, because transform runs on the compositor and shadow forces a repaint. The shadow generator has more on this.

Related templates

Check your version

Once you have edited this, the HTML validator will catch any tag you left unclosed, and the formatter will tidy the indentation. Both run in your browser.