Product Card
A product card is a specialized <article> that displays a product's image, name, and price together with optional description, badges, and action buttons, presenting one product as a self-contained landmark for catalogs, search results, and recommendation strips.
Use it for e-commerce listings, marketplace browse pages, recommendation modules, and any context where products are presented in a uniform card layout.
Example
Show demo markup
<div class="product-card" aria-label="a specialized card for displaying a product with image, title, price, and actions">ProductCard</div>
Usage example
<script lang="ts">
import ProductCard from './ProductCard.svelte';
import Button from '../Button/Button.svelte';
const price = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(24);
</script>
<ProductCard name="Linen Pillow" price={price} imageUrl="/pillow.jpg" imageAlt="Linen pillow on a bed">
<p>Soft and breathable linen pillow.</p>
<Button>Add to cart</Button>
</ProductCard>
Show Svelte source
// In your Svelte component:
import ProductCard from "lily-design-system-svelte-headless/components/ProductCard/ProductCard.svelte";
<ProductCard>
<!-- content -->
</ProductCard>
Usage
Basic product card:
<ProductCard
name="Premium notebook"
price="$19.99"
imageUrl="/img/notebook.jpg"
imageAlt="Premium notebook with embossed cover"
/>
Product card with description and actions:
<ProductCard
name="Premium notebook"
price="$19.99"
imageUrl="/img/notebook.jpg"
imageAlt="Premium notebook with embossed cover"
>
<p>180 pages, dot-grid, hardcover.</p>
<FlexStack direction="row">
<Button>Add to cart</Button>
<IconButton label="Save for later">♡</IconButton>
</FlexStack>
</ProductCard>
Props
name: string (required) -- product name
price: string (required) -- pre-formatted price text
imageUrl: string (optional) -- product image src
imageAlt: string (optional) -- product image alt text
label: string (optional) -- aria-label override (defaults to name)
children: slot -- additional content (description, actions)
...restProps: any additional HTML attributes
Keyboard Interactions
- None on the card itself.
- Focusable controls inside the card (e.g., add-to-cart button) follow their own keyboard behavior.
ARIA
<article> provides an implicit landmark
aria-label defaults to name and may be overridden by label
- The image's
alt text is the consumer's responsibility (pass via imageAlt)
When to Use
- Use to present a single product in catalogs, search results, recommendation strips, and shopping cart summaries.
- Use when products are displayed in a uniform layout and each item has an image, name, and price.
- Use as a child of Grid or Masonry for product listings.
When Not to Use
- Do not use for non-product content (use Card or Tile).
- Do not use as a navigation tile (use Tile or a labeled link).
- Do not omit
imageAlt when an image is provided — alt text is required for non-decorative images.
Headless
Provides only the semantic structure (article, headings, image, slot for extras). Layout, sizing, badge styling, hover states, and any visual treatment are the consumer's responsibility.
Styles
Consumer CSS targets .product-card, .product-card-image, .product-card-header, .product-card-name, and .product-card-price for visual styling.
Testing
- Verify the component renders an
<article> element with class product-card
- Verify
aria-label defaults to name and is overridable by label
- Verify the image renders only when
imageUrl is provided, with the supplied alt
- Verify the name renders inside
<h3 class="product-card-name">
- Verify the price renders inside
<p class="product-card-price">
- Verify children content is rendered after the header
Domain Knowledge
- Pre-format
price to include currency symbol, thousands separators, and decimal places appropriate to the user's locale.
- For sale or strike-through pricing, pass formatted text (e.g.,
<del>$24.99</del> $19.99) via the slot or include it in price.
Advice
- Designers: Maintain a consistent aspect ratio for product images across cards. Keep the price visually prominent and the action button reachable on small screens.
- Developers: Always supply
imageAlt. For decorative product photos that duplicate the name, an empty alt="" is acceptable, but if the image conveys information not in the name, write descriptive alt text.
Implementation Notes
- Renders an
<article> with class="product-card" so each card is a discrete landmark
aria-label defaults to the product name; consumers may override via label
- Optional
imageUrl renders an <img class="product-card-image" alt={imageAlt}>
- The header contains
<h3 class="product-card-name">{name}</h3> and <p class="product-card-price">{price}</p>
- Children render after the header for description, ratings, action buttons, etc.
- Price text is consumer-formatted — the component does not perform currency localization
Related components
card — a grouped content container with header, body, and footer areas
feature-card — a large content card with a prominent image positioned alongside or above the text
References
- MDN article element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article
- WCAG 1.1.1 Non-text Content: https://www.w3.org/WAI/WCAG22/Understanding/non-text-content
Canonical contract
Generated from this component's canonical metadata — the machine-checked source the implementations are held to.
Metadata
- Component: product-card
- PascalCase: ProductCard
- Description: a specialized card for displaying a product with image, title, price, and actions
- Status: beta — implemented and unit-tested in all seven frameworks with an axe-clean demo page; not yet exercised in composed flows
- HTML tag: <article>
- CSS class: .product-card
- Interactive: no (focusable children manage their own behavior)
ARIA
<article> implicit landmark aria-label defaults to name, overridable by label prop - Image
alt supplied via imageAlt prop
Keyboard
- None on card
- Focusable children manage their own keyboard behavior
Props
name: string (required) price: string (required) -- pre-formatted imageUrl: string (optional) imageAlt: string (optional) label: string (optional) -- aria-label override children: slot -- additional content ...restProps: any additional HTML attributes