Products
Fruit Vegetable More

Product Page

Edit on Github
Open in Playground View Demo

Experimental Mode

This example uses the following experimental feature: [amp-bind]. Enable the experiment via the button below. Some components require the AMP Dev Channel to be enabled as well. Learn more here.

Enable Dev Channel

Introduction

This sample showcases how to build a product page in AMP HTML.

Metadata

The page indexing requires schema.org markup for one of the following types: Type, AggregateRating, Offers. Learn more.

<script type="application/ld+json">
{
    "@context": "http://schema.org/",
    "@type": "Product",
    "name": "Apple",
    "image": "http://www.ampbyexample.com/img/golden_apple1_1024x682.jpg",
    "description": "Lorem ipsum",
    "mpn": "925872",
    "brand": {
        "@type": "Fruit",
        "name": "Apple"
    },
    "aggregateRating": {
        "@type": "AggregateRating",
        "ratingValue": "4.4",
        "reviewCount": "88"
    },
    "offers": {
        "@type": "Offer",
        "priceCurrency": "USD",
        "price": "1.99",
        "priceValidUntil": "2020-11-05",
        "itemCondition": "http://schema.org/UsedCondition",
        "availability": "http://schema.org/InStock",
        "seller": {
            "@type": "Retail",
            "name": "AMP by Example"
        }
    }
}
</script>

Use the amp-sidebar component to give customers the chance to quickly jump to any of your product categories.

Example

<amp-sidebar id="drawermenu"
  layout="nodisplay">
  <a href="/"
    class="caps text-decoration-none block p1">Products</a>
  <hr/>
  <a class="caps text-decoration-none block p1"
    href="/samples_templates/product_browse_page/preview/">Fruit</a>
  <a class="caps text-decoration-none block p1"
    href="/samples_templates/product_browse_page/preview/">Vegetable</a>
  <a class="caps text-decoration-none block p1"
    href="/samples_templates/product_browse_page/preview/">More</a>
</amp-sidebar>

AMP supports forms, which means you can directly integrate a product search into your AMPs. Try searching for "Apple".

AMP doesn't support custom Javascript, but you can use CSS3 animations to enrich your page. The expanding text field when the search box is focused is implemented with CSS only.

Example

<div class="header">
  <a id="sample-menu"
    on="tap:drawermenu.toggle">
    <amp-img srcset="/img/ic_menu_white_1x_web_24dp.png 1x, /img/ic_menu_white_2x_web_24dp.png 2x"
      width="24"
      height="24"
      alt="navigation"></amp-img>
  </a>
  <form method="GET"
    action="/samples_templates/product_browse_page/search"
    target="_top">
    <input name="search"
      type="search"
      placeholder="Search">
    <a id="sample-logo"
      href="/">Product</a>
    <input type="submit"
      value="">
  </form>
</div>

Social Sharing

The Social Share extension provides a common interface for share buttons. Learn more about amp-social-share here.

Example

<div class="m1">
  <amp-social-share type="twitter"
    width="30"
    height="22"></amp-social-share>
  <amp-social-share type="facebook"
    width="30"
    height="22"
    data-attribution="254325784911610"></amp-social-share>
  <amp-social-share type="gplus"
    width="30"
    height="22"></amp-social-share>
  <amp-social-share type="email"
    width="30"
    height="22"></amp-social-share>
  <amp-social-share type="pinterest"
    width="33"
    height="22"></amp-social-share>
</div>

Video

AMP supports a wide range of video platforms. Here we are using amp-youtube to show a product video. You can find an overview of all supported video platforms here.

In this sample we don't embed the video directly into the page, but instead show it in a lightbox using the amp-lightbox component. Learn more about amp-lightbox here.

Example

  <button class="ampstart-btn caps m1 mb3"
    on="tap:watch-video-lightbox">
    Show Video
  </button>

On desktop we've added a film strip like slide preview. Each product colour has its own image gallery and slide preview. We use amp-bind to update the page selecting the gallery and image preview corresponding to the selected colour. We realized the color selection by using amp-selector. Find more the doc and info in The Product Page section below.

The green colour is the default color for the gallery when the page loads. Clicking on a preview image will reveal the image in the gallery via the tap action on="tap:AMP.setState({product: {selectedSlide: 0}})".

See below for more informations on how we useamp-bind.

Example

<div class="product-gallery">
  <ul class="show"
    [class]="product.selectedColor == 'green' ? 'show' : 'hide'">
    <li tabindex="0"
      role="button">
      <amp-img on="tap:AMP.setState({product: {selectedSlide: 0}})"
        src="/img/green_apple_1_60x40.jpg"
        width="60"
        height="40"
        class="selected"
        [class]="product.selectedSlide == 0 ? 'selected' : '' ">
      </amp-img>
    </li>
    <li tabindex="1"
      role="button">
      <amp-img on="tap:AMP.setState({product: {selectedSlide: 1}})"
        src="/img/green_apple_2_60x40.jpg"
        width="60"
        height="40"
        [class]="product.selectedSlide == 1 ? 'selected' : '' ">
      </amp-img>
    </li>
  </ul>

  <ul class="hide"
    [class]="product.selectedColor == 'golden' ? 'show' : 'hide'">
    <li tabindex="0"
      role="button">
      <amp-img on="tap:AMP.setState({product: {selectedSlide: 0}})"
        src="/img/product1_alt1_60x40.jpg"
        width="60"
        height="40"
        [class]="product.selectedSlide == 0 ? 'selected' : '' ">
      </amp-img>
    </li>
  </ul>

  <ul class="hide"
    [class]="product.selectedColor == 'red' ? 'show' : 'hide'">
    <li tabindex="0"
      role="button">
      <amp-img on="tap:AMP.setState({ product: {selectedSlide : 0}})"
        src="/img/red_apple_1_60x40.jpg"
        width="60"
        height="40"
        [class]="product.selectedSlide == 0 ? 'selected' : '' ">
      </amp-img>
    </li>
    <li tabindex="1"
      role="button">
      <amp-img on="tap:AMP.setState({ product: {selectedSlide : 1}})"
        src="/img/red_apple_2_60x46.jpg"
        width="60"
        height="40"
        [class]="product.selectedSlide == 1 ? 'selected' : '' ">
      </amp-img>
    </li>
  </ul>
</div>

The amp-carousel component works very well for product image galleries. Learn how the amp-carousel component works here.

Notice how we are binding to the slide and class by using to the values of the variables selectedSlide and selectedColor.

Example

<div class="product-gallery">

  <amp-carousel id="green-apple-carousel"
    width="1024"
    height="682"
    layout="responsive"
    type="slides"
    [slide]="product.selectedSlide"
    on="slideChange: AMP.setState({product: {selectedSlide: event.index}})"
    class="show"
    [class]="product.selectedColor == 'green' ? 'show' : 'hide'">
    <amp-img src="/img/green_apple_1_1024x682.jpg"
      width="1024"
      height="682"
      layout="responsive"
      on="tap:gallery-lightbox"
      role="button"
      tabindex="0">
    </amp-img>
    <amp-img src="/img/green_apple_2_1024x685.jpg"
      width="1024"
      height="682"
      layout="responsive"
      on="tap:gallery-lightbox"
      role="button"
      tabindex="0">
    </amp-img>
  </amp-carousel>

  <amp-carousel id="golden-apple-carousel"
    width="1024"
    height="682"
    layout="responsive"
    type="slides"
    [slide]="product.selectedSlide"
    on="slideChange: AMP.setState({product: {selectedSlide: event.index}})"
    class="hide"
    [class]="product.selectedColor == 'golden' ? 'show' : 'hide'">
    <amp-img src="/img/golden_apple1_1024x682.jpg"
      width="1024"
      height="682"
      layout="responsive"
      on="tap:gallery-lightbox"
      role="button"
      tabindex="0">
    </amp-img>
  </amp-carousel>

  <amp-carousel id="red-apple-carousel"
    width="1024"
    height="682"
    layout="responsive"
    type="slides"
    [slide]="product.selectedSlide"
    on="slideChange: AMP.setState({product: {selectedSlide: event.index}})"
    class="hide"
    [class]="product.selectedColor == 'red' ? 'show' : 'hide'">
    <amp-img src="/img/red_apple_1_1024x682.jpg"
      width="1024"
      height="682"
      layout="responsive"
      on="tap:gallery-lightbox"
      role="button"
      tabindex="0">
    </amp-img>
    <amp-img src="/img/red_apple_2_1024x793.jpg"
      width="1024"
      height="682"
      layout="responsive"
      on="tap:gallery-lightbox"
      role="button"
      tabindex="0">
    </amp-img>
  </amp-carousel>

Full screen image

The amp-image-lightbox component allows the user to expand an image to fill the viewport. By clicking on each image from the product gallery, the user can see the product image by using the full screen. Learn more here

Example

<amp-image-lightbox id="gallery-lightbox"
  layout="nodisplay">
  <div on="tap:gallery-lightbox.close"
    role="button"
    tabindex="0">
    <button class="ampstart-btn caps m2 close-gallery-button"
      on="tap:gallery-lightbox.close"
      role="button"
      tabindex="0">
      Close
    </button>
  </div>
</amp-image-lightbox>

Product Configuration

We use the amp-state component (part of amp-bind) to configure the product price depending on the color and the size. We also configure the defaultSize for each product when you switch between colours and the size you previously selected is not available for the new color

Example

<amp-state id="product">
  <script type="application/json">
    {
      "relatedProductsSrc": "/json/related_products.json",
      "selectedColor": "green",
      "selectedSize": "S",
      "selectedSlide": 0,
      "green": {
        "sizes": {
          "S": "$5.99",
          "M": "$5.99",
          "L": "unavailable"
        },
        "defaultSize": "S"
      },
      "golden": {
        "sizes": {
          "S": "$9.99",
          "M": "unavailable",
          "L": "$9.99"
        },
        "defaultSize": "L"
      },
      "red": {
        "sizes": {
          "S": "$7.99",
          "M": "$7.99",
          "L": "$7.99"
        },
        "defaultSize": "M"
      }
    }
  </script>
</amp-state>

Product price

We use amp-bind to update the price of the product depending on the selected colour.

We bind the text attribute to the value of expression product[product.selectedColor].sizes[product.selectedSize]. product is the id amp-state json described in the Product Configuration section.

Notice how we set $1.99 value as the default for the price: expressions will not be evaluated at the page load.

We use amp-selector together with amp-bind to update the page for showing the price, the size availability, the gallery and preview of images. Every time you select a color, we call AMP.setState with {selectedColor: event.targetOption}. This is triggering all the attributes and elements binded to selectedColor to update: the price (binded with the attribute [text]), the gallery, the image preview and the size selector (all binded with [class]).

Example

Price: $1.99

<p class="price-description">Price:
  <span [text]="product[product.selectedColor].sizes[product.selectedSize]">$1.99</span>
</p>

Product page

The add-to-cart action action is implemented using amp-form. In our sample we use the amp-selector for selecting different product properties. Pressing the ADD TO CART button adds the product to a shopping cart page using the properties you have selected. Notice that the button URL contains the query clientId={{ClientId}}. If an item is successfully added to the shopping cart, we redirect the user to the shopping cart page. The redirect target is configured via a header in the server's form response (redirecting after submission):

access-control-expose-headers:AMP-Access-Control-Allow-Source-Origin,AMP-Redirect-To
amp-redirect-to:http://ampbyexample.com/shopping_cart/?clientid=amp-123456789 

We use the CLIENT_ID variable to identify the user, this enables storing a shopping cart between repeated visits of an AMP page (either via the AMP Cache or the original host). This variable can be used inside an amp-form by declaring an hidden input value with default-value="CLIENT_ID(cart). Read more about variable substitution here.

Example

  • 1
  • 2
  • 3
  • S
  • M
  • L
  <form id="order"
    method="POST"
    action-xhr="/samples_templates/product_page/add_to_cart"
    target="_top"
    class="flex flex-wrap m1">
    <div class="items-center flex">
      <label for="color">Color:</label>
      <amp-selector name="color"
        layout="container"
        [selected]="product.selectedColor"
        on="select:AMP.setState({
                    product: {
                      selectedColor: event.targetOption,
                      selectedSlide: 0,
                      selectedSize: product[event.targetOption].sizes[product.selectedSize] != 'unavailable' ? product.selectedSize : product[event.targetOption].defaultSize
                    }
                  })">
        <ul class="p0 m1">
          <li>
            <div option="green"
              selected
              class="square green"
              width="40"
              height="40"></div>
          </li>
          <li>
            <div option="golden"
              class="square golden"
              width="40"
              height="40"></div>
          </li>
          <li>
            <div option="red"
              class="square red"
              width="40"
              height="40"></div>
          </li>
        </ul>
      </amp-selector>
    </div>
    <div class="items-center flex">
      <label for="quantity">Quantity:</label>
      <amp-selector name="quantity"
        layout="container">
        <ul class="p0 m1">
          <li option="1"
            selected>1</li>
          <li option="2">2</li>
          <li option="3">3</li>
        </ul>
      </amp-selector>
    </div>
    <div class="items-center flex">
      <label for="size">Size:</label>
      <amp-selector name="size"
        layout="container"
        on="select:AMP.setState({ product: {selectedSize: event.targetOption}})"
        [selected]="(product[product.selectedColor].sizes[product.selectedSize] != 'unavailable')
                      ? product.selectedSize
                      : product[product.selectedColor].defaultSize">
        <ul class="p0 m1">
          <li option="S"
            class=""
            [class]="(product[product.selectedColor].sizes['S'] != 'unavailable')
                      ? ''
                      : 'unavailable'">S</li>
          <li option="M"
            class=""
            selected
            [class]="(product[product.selectedColor].sizes['M'] != 'unavailable')
                      ? ''
                      : 'unavailable'">M</li>
          <li option="L"
            class="unavailable"
            [class]="(product[product.selectedColor].sizes['L'] != 'unavailable')
                      ? ''
                      : 'unavailable'">L</li>
        </ul>
      </amp-selector>
    </div>
    <div class="items-center flex my1">
      <input type="submit"
        class="ampstart-btn caps"
        name="add-to-cart"
        value="add to cart">
    </div>
    <input type="hidden"
      name="name"
      value="Apple">
    <input type="hidden"
      name="price"
      value="$1.99">
    <input name="clientId"
      type="hidden"
      value="CLIENT_ID(cart)"
      data-amp-replace="CLIENT_ID">
    <div submit-error>
      <template type="amp-mustache">
        Error! Looks like something went wrong with your shopping cart, please try to add an item again. {{error}}
      </template>
    </div>
  </form>

Tab panels

Use amp-selector styled as tab panels to add additional data about your product. Learn how to implement tabs in AMP here.

Example

Fruit is rich in vitamins and minerals. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Always wash fruit before eating. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Size may vary. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
<amp-selector role="tablist"
  layout="container"
  class="ampTabContainer ampstart-headerbar-nav">
  <div role="tab"
    class="tabButton h4 ampstart-nav-item"
    selected
    option="a">ABOUT</div>
  <div role="tabpanel"
    class="tabContent p1 p">Fruit is rich in vitamins and minerals. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
    ex ea commodo consequat.</div>
  <div role="tab"
    class="tabButton h4 ampstart-nav-item"
    option="b">SPECS</div>
  <div role="tabpanel"
    class="tabContent p1 p">Always wash fruit before eating. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
    consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</div>
  <div role="tab"
    class="tabButton h4 ampstart-nav-item"
    option="c">SIZE</div>
  <div role="tabpanel"
    class="tabContent p1 p">Size may vary. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</div>
</amp-selector>

With AMP, you can easily pull in different latest offers or highlights without changing the page. To do so, just use amp-list to fire a CORS request to a JSON endpoint which supplies the list of related products. These are populated into an amp-mustache template on the client. You can dynamically change the source of the amp-list endpoint by using amp-bind: we bind the variable relatedProductsSrc to the to the src attribute of the amp-list. Learn more about amp-list here.

Example

<amp-list class="items m1"
  width="auto"
  height="100"
  layout="fixed-height"
  src="/json/related_products.json"
  [src]="product.relatedProductsSrc">
  <template type="amp-mustache">
    <a class="text-decoration-none p1"
      href="/samples_templates/product_page/preview/">
      <amp-img width="640"
        height="426"
        layout="responsive"
        alt="{{name}}"
        src="{{img}}"></amp-img>
      <p class="name">{{name}}</p>
      <p class="star">{{{stars}}}</p>
      <p class="price">${{price}}</p>
    </a>
  </template>
</amp-list>

The button tap action sets the value of the relatedProductsSrc to an extended list of related products.

Example

<button class="ampstart-btn caps m1 mb3"
  id="listSrcButton"
  on="tap:AMP.setState({product: {relatedProductsSrc: '/json/more_related_products.json'}})">Show more</button>

User Analytics

Analytics must be configured in the body. Here we use Google Analytics to track pageviews.

Example

<amp-analytics type="googleanalytics">
  <script type="application/json">
    {
      "vars": {
        "account": "UA-73836974-1"
      },
      "triggers": {
        "default pageview": {
          "on": "visible",
          "request": "pageview",
          "vars": {
            "title": "Product"
          }
        }
      }
    }
  </script>
</amp-analytics>