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

ProductCard
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