Product card
Stable Available in release assets
Summarize a server-authored product with a durable detail-page link, honest price, inventory message, and resilient media treatment.
Product card in context
<article class="thin-product-card">
<img src="product.jpg" alt="Blue canvas tote">
<div class="thin-product-badges">
<span class="thin-badge">Sale</span>
</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">
<span aria-label="Sale price">$68.00</span>
<s aria-label="Original price">$82.00</s>
</p>
<p class="thin-inventory-status" data-state="low-stock">Only 3 left</p>
</div>
</article>Show code
<article class="thin-product-card">
<img src="product.jpg" alt="Blue canvas tote">
<div class="thin-product-badges">
<span class="thin-badge">Sale</span>
</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">
<span aria-label="Sale price">$68.00</span>
<s aria-label="Original price">$82.00</s>
</p>
<p class="thin-inventory-status" data-state="low-stock">Only 3 left</p>
</div>
</article>Variant gallery
Each authored example uses the production assets and includes copyable markup.
Default product card
Linked media, heading, price, and inventory remain in semantic document flow.
<article class="thin-product-card">
<img src="product.jpg" alt="Blue canvas tote">
<div class="thin-product-badges">
<span class="thin-badge">Sale</span>
</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">
<span aria-label="Sale price">$68.00</span>
<s aria-label="Original price">$82.00</s>
</p>
<p class="thin-inventory-status" data-state="low-stock">Only 3 left</p>
</div>
</article>Show code
<article class="thin-product-card">
<img src="product.jpg" alt="Blue canvas tote">
<div class="thin-product-badges">
<span class="thin-badge">Sale</span>
</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">
<span aria-label="Sale price">$68.00</span>
<s aria-label="Original price">$82.00</s>
</p>
<p class="thin-inventory-status" data-state="low-stock">Only 3 left</p>
</div>
</article>Sale pricing
Current and previous prices use explicit accessible labels.
<article class="thin-product-card">
<a href="/products/tote">
<img src="product.jpg" alt="Blue canvas tote">
</a>
<div class="thin-product-badges">
<span class="thin-badge">Sale</span>
</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">
<span aria-label="Sale price">$68.00</span>
<s aria-label="Original price">$82.00</s>
</p>
</div>
</article>Show code
<article class="thin-product-card">
<a href="/products/tote">
<img src="product.jpg" alt="Blue canvas tote">
</a>
<div class="thin-product-badges">
<span class="thin-badge">Sale</span>
</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">
<span aria-label="Sale price">$68.00</span>
<s aria-label="Original price">$82.00</s>
</p>
</div>
</article>Missing media fallback
A named fallback keeps the card complete when media is unavailable.
<article class="thin-product-card">
<div class="thin-product-media-fallback" role="img" aria-label="Product image unavailable">Image unavailable</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">$68.00</p>
</div>
</article>Show code
<article class="thin-product-card">
<div class="thin-product-media-fallback" role="img" aria-label="Product image unavailable">Image unavailable</div>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Canvas tote</a></h2>
<p class="thin-product-price">$68.00</p>
</div>
</article>Long localized product and price
Long names and localized currency wrap without clipping the detail link.
<article class="thin-product-card">
<a href="/products/tote">
<img src="product.jpg" alt="Blue canvas tote">
</a>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Robuste Alltagstasche für außergewöhnlich lange, mehrsprachige Produktbezeichnungen</a></h2>
<p class="thin-product-price"><span aria-label="Price in Japanese yen">JPY 123,456,789.00 einschließlich Steuern</span></p>
</div>
</article>Show code
<article class="thin-product-card">
<a href="/products/tote">
<img src="product.jpg" alt="Blue canvas tote">
</a>
<div class="thin-product-card-body">
<h2><a href="/products/tote">Robuste Alltagstasche für außergewöhnlich lange, mehrsprachige Produktbezeichnungen</a></h2>
<p class="thin-product-price"><span aria-label="Price in Japanese yen">JPY 123,456,789.00 einschließlich Steuern</span></p>
</div>
</article>Anatomy
- Linked product media or named fallback
- Optional badges
- Product heading and durable link
- Current and optional previous price
- Server-rendered availability
Variants and sizes
- Default — shown above
- Sale — shown above
- No image — shown above
- Long localized product and price — shown above
States and difficult content
Rendered fixtures exercise authored content and constrained layouts with the same production assets.
- Available
- Sale
- Sold out
- Low stock
- Loading collection
- Difficult content
- Narrow container
Structured state fixtures are not published for this item yet.
Interaction and keyboard behavior
The product link and optional native controls work without enhancement; selection, inventory, and pricing remain server truth.
Accessibility
Use meaningful image alternatives, a descriptive product link, explicit price labels when multiple amounts appear, and text for availability.
- Contract
COMPOSE- Basis
- Native links, headings, forms, lists, and status content; no product-specific APG widget
- Evidence
tests/unit/library/ecommerce-patterns.test.ts
API
Use .thin-product-card on semantic article markup and group card content in .thin-product-card-body. Optional quick-add steppers, mini-cart drawers, and gallery layouts compose with quantity-input, drawer, and product-gallery, which remain preview. Mark stock with .thin-inventory-status and data-state=low-stock or data-state=sold-out; the text must still say the state.
Behavior tier: datastar-optional
Structured host, property, and example tables are not authored for this item yet. Use the summary above and the primary example.
Fallback
Products, prices, forms, pagination, and empty states remain in document flow.
Assets and dependencies
/foundation.css/layouts.css/components.css/patterns/ecommerce.css
Dependencies: quantity-input, drawer, product-gallery
Source: packages/ui/src/style/ecommerce.ts
Stability evidence
- Semantic server-authored markup, intrinsic CSS, and no-JavaScript contract:
test:tests/unit/library/ecommerce-patterns.test.ts - Complete item-specific API, fallback, difficult states, and preview composition honesty:
test:tests/unit/library/composition-stable.test.ts - Cross-theme, density, narrow-container, zoom, and forced-colors fixture:
review:site/src/pages/quality/index.astro#quality-review
Composition
Derived from the examples and style specs, not authored.