Skip to main content
WireKit
Copy for LLM

Product Card

The card a product grid is made of. It composes image, price, rating, badge and button — so the pieces you already know keep behaving the way you already know.

Product cards on sale, low stock and sold out
Sale

Brass desk lamp

Warm, dimmable, and heavy enough to stay where you put it.

128 reviews
€59.00 €39.00
Low stock

Walnut notebook

Dotted, lies flat, survives a bag.

12 reviews
€12.00
Out of stock

Studio chair

The one you stop noticing after a week.

41 reviews
€249.00

Prices and sales

Pass price, and compare-at for what it used to cost:

<x-wirekit::product-card name="Brass desk lamp" :price="3900" :compare-at="5900" :minor-units="true" currency="EUR" />

The old price renders struck through in a real <del>, and the whole story is announced: "€39.00, was €59.00, 34% off". That comes from price, which already does it properly — so the "Sale" badge on the corner is pure decoration and carries nothing a screen-reader user would miss.

A compare-at that is equal to or lower than price is not treated as a sale. It is not a discount, and striking it through would be a claim the numbers do not support.

Stock

availability What happens
in-stock Nothing (the default)
low-stock A "Low stock" badge. The CTA stays live — it is a nudge, not a wall.
out-of-stock An "Out of stock" badge, and the CTA is disabled and says why

Both states are words, not a wash over the image. A reader who cannot resolve a gray tint sees a perfectly normal product.

Images

image-alt defaults to empty on purpose. The product name is already right there in text, so an alt that repeats it makes a screen reader read the product twice.

Pass one when the image shows something the name does not:

<x-wirekit::product-card
    name="Brass desk lamp"
    image="/img/lamp.jpg"
    image-alt="Lit and angled over a dark desk"
    :price="3900"
/>

href makes the whole card clickable — but the link is the product name, not the card.

That is deliberate. Wrapping the card in one anchor makes the link's accessible name the entire card (image, price, rating, button text, all read as one breathless sentence), and it swallows the add-to-cart button, which cannot nest inside an anchor at all. The pointer gets the big target; the screen reader gets a short, honest name.

A different action

The action slot replaces the default CTA:

<x-wirekit::product-card name="Studio chair" :price="24900" availability="out-of-stock">
    <x-slot:action>
        <x-wirekit::button intent="neutral" class="w-full">Notify me</x-wirekit::button>
    </x-slot:action>
</x-wirekit::product-card>

Props

Prop Type Default Description
name string '' The product. Rendered as the heading.
href string|null null Product page. Makes the card clickable; the name is the link.
image string|null null Cover image.
imageAlt string|null null Accessible name for the image. Empty by default — the name is already in text.
price int|float|null null Current price.
compareAt int|float|null null What it used to cost. Only treated as a sale when it is higher.
currency string config Currency code.
minorUnits bool false Treat the amounts as cents.
rating int|float|null null Average out of 5.
reviewCount int|null null How many people rated it.
description string|null null Short line under the name. Clamped to two lines.
availability string 'in-stock' in-stock, low-stock, out-of-stock.
scope string|null null Class-scope override.

Slots

Slot Description
action Replaces the default add-to-cart button.

Accessibility

  • An <article>, so a screen reader can move between products as units.
  • The name is the link, never the whole card — see above.
  • The sale is announced by price ("was €59.00, 34% off"), so it never depends on seeing a strike-through or a red badge.
  • Stock states are words in a badge, not a tint.
  • A disabled CTA says why in its accessible name ("Out of stock: Brass desk lamp"). A disabled button with no explanation is a dead end.
  • The review count sits next to the stars: "4.5 stars" from two people and from two thousand are different claims.
  • A product nobody has rated renders no star row at all — an empty row implies zero stars, and unrated is not zero.

Keyboard Interaction

Key Action
Tab Move to the product name, then to the CTA.
Enter Follow the name to the product page.
Enter / Space Fire the CTA when it has focus.

Two stops per card, not one per element — the image, badges and rating are not focusable, because none of them do anything.