> ## Documentation Index
> Fetch the complete documentation index at: https://docs.depict.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Setting up your product card

> Your product card components needs to meet some specific requirements

<Warning>
  This page does not apply to installs made in 2025 or later of the Depict Shopify apps
</Warning>

## Requirements

For both search and product listing pages, you need to set up your product card component.

We call the product data that you receive for each product card that you have to render `display`.
That component will receive a `display` of the type `YourDisplay | null`.
`YourDisplay` is a type definition of your specific display. Please ask Depict for this.

If `display` is `null`, it means that there's no data for this product card yet, and you should display <b>a product card as close to the dimensions as the one you'll display when you'll have data</b>, we'll call this a "placeholder". The placeholder serves as a loading indicator for users as well as enabling scroll restoration when going back and minimizing content shift.

The `productCard` component has to return a `HTMLElement` or `HTMLElement[]`.

## Example expected output of your product card component

<CardGroup cols={2}>
  <Frame caption="`display` is `null`">
    <img
      src="https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=8b96b949b362cefa656637ab62081257"
      style={{ aspectRatio: 457/903, width: "100%"}}
      ref={element => {
    // In prod, they have some zoom thing which breaks aspectRatio, work around that. Unfortunately this is not in the SSR'd output so there's still some CLS.
    const direct_parent = element?.parentElement;
    if(direct_parent?.matches?.("[data-rmiz-content]")) {
        Object.assign(direct_parent.style, {
            width: "100%",
            display: "flex",
            justifyContent: "center",
            alignItems: "center"
        })
    }
    const parents_parent = direct_parent?.parentElement;
    if(parents_parent?.matches?.("[aria-owns^='rmiz-modal']")) {
        parents_parent.style.width = "100%";
    }
    }}
      data-og-width="457"
      width="457"
      data-og-height="903"
      height="903"
      data-path="images/placeholder.svg"
      data-optimize="true"
      data-opv="3"
      srcset="https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?w=280&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=c40148726d1f8a68353bde9b1b34d718 280w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?w=560&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=635f7791e3f386286cfd1549c6a7238b 560w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?w=840&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=6a20d33dd641f3a10a201142f845e987 840w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?w=1100&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=5ce3275c0191bf0d66dcae6049184742 1100w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?w=1650&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=f7a0e748b73764ad382c44346d7c3f37 1650w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/placeholder.svg?w=2500&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=e297af892bfdd0a8fd48126cfbbbbb18 2500w"
    />
  </Frame>

  <Frame caption="`display` is `HoudiniDisplay`">
    <img
      src="https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=a3c900bb57e3726b1db2f38f68562d87"
      style={{ aspectRatio: 469/928, width: "100%"}}
      ref={element => {
    // In prod, they have some zoom thing which breaks aspectRatio, work around that. Unfortunately this is not in the SSR'd output so there's still some CLS.
    const direct_parent = element?.parentElement;
    if(direct_parent?.matches?.("[data-rmiz-content]")) {
        Object.assign(direct_parent.style, {
            width: "100%",
            display: "flex",
            justifyContent: "center",
            alignItems: "center"
        })
    }
    const parents_parent = direct_parent?.parentElement;
    if(parents_parent?.matches?.("[aria-owns^='rmiz-modal']")) {
        parents_parent.style.width = "100%";
    }
    }}
      data-og-width="469"
      width="469"
      data-og-height="928"
      height="928"
      data-path="images/product_card.svg"
      data-optimize="true"
      data-opv="3"
      srcset="https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?w=280&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=79e2de8ed041a8405134a66bef73c691 280w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?w=560&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=a876db34a2be5b46bbcf0c742c18422c 560w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?w=840&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=048a8faa4cd4fc9a35f4b80361e7d707 840w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?w=1100&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=0eb7c50d4873240d5ec1ff725e79e9dc 1100w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?w=1650&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=73eaca34dffbebe680b83bffed047604 1650w, https://mintcdn.com/depictai/oqYy6W_ukzaDVZL8/images/product_card.svg?w=2500&fit=max&auto=format&n=oqYy6W_ukzaDVZL8&q=85&s=88a578d23f1b01027093c4fb1fbd3d56 2500w"
    />
  </Frame>
</CardGroup>

## Provided Placeholder components

You can use the `ImagePlaceholder` component to display a placeholder image while the product image is loading.
You can use the `TextPlaceholder` component to display a placeholder text while the product title or price is loading.

[Read the reference for more information](/reference/listings-search/placeholder-components).

## Displaying color swatches

```ts theme={null}
export interface YourDisplay {
  /**
  * The index of the variant to show in the variant_displays array
  */
  variant_index: number;
  /**
  * All variants for that product - here you can, for example, get the color of each variant to display color swatches
  */
  variant_displays: YourVariantDisplay[];
  // … other properties
}
```

## Code example

You probably have an existing product card which you just can add placeholder functionality for. Here's an example of how it could look like:

<Tip>"Why is this JSX? I'm not using react!" - Did you miss the [previous page?](/js-ui-guide/setup/setting-up-jsx)</Tip>

```tsx theme={null}
import { ImagePlaceholder, TextPlaceholder } from "@depict-ai/js-ui";


export function ProductCard({ display }: { display: null | YourDisplay }) {
  const variant_to_show = display?.variant_displays[display?.variant_index];

  return [
    <a
      href={variant_to_show?.page_url || "#"}
      class="rec-outer"
      {...(variant_to_show && "recommendation_id" in variant_to_show
        ? { "data-recommendation-id": variant_to_show.recommendation_id }
        : {})}
    >
      {variant_to_show ? (
        <div class="image-container">
          {/* Use the first image in image_urls as the default image */}
          <img class="default-image" src={variant_to_show.image_urls[0]} />
          {/* Use the second image in image_urls as the hover image */}
          <img class="hover-image" src={variant_to_show.image_urls[1]} />
        </div>
      ) : (
        <ImagePlaceholder aspectRatio={imgAspectRatio} />
      )}

      <div class="text-container">
        <div class="rec-title">
          {variant_to_show.title ?? <TextPlaceholder height="1em" width="20ch" />}
        </div>
        <div class="price-container">
          <span class="price">
            {variant_to_show ? (
              <FormatPrice price={variant_to_show.sale_price} />
            ) : (
              <TextPlaceholder height="1em" width="5ch" />
            )}
          </span>
        </div>
      </div>
    </a>,
  ];
}
```

<Note>Note how we branch off to the shimmering placeholder effect in place of the content, when there is no content</Note>
