Skip to content

Product Section (sections/product.liquid) ​

sections/product.liquid renders Shopify's main product detail page with a flexible block-based structure. It combines product media galleries, variant selection, pricing, purchasing options, and dynamic content blocks. The section uses a custom element (<product-info>) to manage variant state and coordinate updates via the Section Rendering API.


Dependencies & Assets ​

TypeFiles / Components
CSSswiper7.4.1.min.css, component-product-price.css, section-product.css, component-product-media-modal.css (conditional), component-product-card.css (conditional), component-complementary-products.css (conditional), component-quick-add.css (conditional), component-pickup-availability.css (conditional), component-product-share-button.css (conditional)
JavaScriptsection-product.js, swiper7.4.1.min.js, component-product-card.js (conditional), product-recommendations.js (conditional), component-quick-add.js (conditional), component-modal-opener.js (conditional), component-pickup-availability.js (conditional), component-product-share-button.js (conditional), component-product-media-modal.js (conditional), component-product-media-magnify.js (conditional), component-selling-plans.js (conditional)
Custom Elements<product-info>, <variant-selector>, <quantity-selector>, <pickup-availability>, <product-recommendations>, <selling-plans-widget>
Snippetscomponent-product-media-gallery, component-product-price, component-product-share-button, component-product-card, component-pickup-availability, component-product-media-modal
Iconsicon-spinner.svg, icon-unavailable.svg, icon-caret.svg, icon-inventory-status.svg (inline via inline_asset_content)
  • Many assets are conditionally loaded based on section settings and block types.
  • Custom elements handle interactive behavior and state management.
  • Section uses Swiper for media carousel functionality.

Dynamic Styles ​

Dynamic styles are generated via an inline {% style %} block:

liquid
{%- style -%}
  .section-{{ section.id }}-padding {
    padding-top: {{ section.settings.padding_top | times: 0.75 | round: 0 }}px;
    padding-bottom: {{ section.settings.padding_bottom | times: 0.75 | round: 0 }}px;
  }

  .option__label {
    font-size: {{ section.settings.variant_label_font_size }}px;
    font-weight: {{ section.settings.variant_label_font_weight }};
  }

  {% for block in section.blocks %}
    {% case block.type %}
      {% when 'title' %}
        .product__title h1 {
          font-size: {{ block.settings.title_font_size }}px;
          font-weight: {{ block.settings.title_font_weight }};
        }
      {% when 'description' %}
        .product__description {
          font-size: {{ block.settings.description_font_size }}px;
          font-weight: {{ block.settings.description_font_weight }};
        }
      {% when 'price' %}
        .price-item {
          font-size: {{ block.settings.price_font_size }}px;
          font-weight: {{ block.settings.price_font_weight }};
        }
    {% endcase %}
  {% endfor %}

  @media screen and (min-width: 769px) {
    .section-{{ section.id }}-padding {
      padding-top: {{ section.settings.padding_top }}px;
      padding-bottom: {{ section.settings.padding_bottom }}px;
    }
  }
{%- endstyle -%}
  • Responsive padding: Mobile padding is 75% of desktop padding for better mobile spacing.
  • Block-specific styles: Title, description, and price blocks have customizable colors, font sizes, and font weights.
  • Variant label styling: Global variant label styling applies to all variant pickers.

Markup Structure ​

liquid
<product-info
  data-url="{{ product.url}}"
  data-section="{{ section.id }}"
  class="color-{{ section.settings.color_scheme }} section-{{ section.id }}-padding"
>
  <!-- CSS and JavaScript assets -->
  <!-- Dynamic styles -->
  
  <div class="page-width">
    <div class="product flex product--{{ section.settings.media_size }} product--{{ section.settings.media_position }} {% if section.settings.constrain_to_viewport %}constrain-height{% endif %}">
      <div class="product__media-wrapper flex__item media-{{ section.settings.media_fit }}">
        {% render 'component-product-media-gallery', ... %}
      </div>
      <div class="product__info-wrapper flex__item {% if section.settings.enable_sticky_info %}product__info-sticky {% endif %}">
        {%- for block in section.blocks -%}
          {%- case block.type -%}
            <!-- Block rendering -->
          {%- endcase -%}
        {%- endfor -%}
      </div>
    </div>
  </div>
  
  <!-- Media modal (conditional) -->
  <!-- JavaScript assets -->
</product-info>
  • Custom element wrapper: <product-info> manages variant state and coordinates updates.
  • Two-column layout: Media gallery on one side, product info on the other.
  • Flexible media positioning: Media can be positioned left or right via media_position setting.
  • Sticky info: Product info can stick to viewport on scroll when enabled.

Block Types ​

The section supports the following block types (rendered in order):

Text Block ​

liquid
{%- when 'text' -%}
  <p class="product__text {% if block.settings.text_style == 'uppercase' %} caption-with-letter-spacing{% elsif block.settings.text_style == 'subtitle' %} subtitle{% endif %}" {{ block.shopify_attributes }}>
    {{- block.settings.text -}}
  </p>

Title Block ​

liquid
{%- when 'title' -%}
  <div class="product__title" {{ block.shopify_attributes }}>
    <h1>{{ product.title }}</h1>
  </div>

Price Block ​

liquid
{%- when 'price' -%}
  <div id="price-{{ section.id }}" class="product__price" role="status" {{ block.shopify_attributes }}>
    {%- render 'component-product-price', product: product, use_variant: true, show_badges: true, price_class: 'price--large' -%}
  </div>
  <!-- Volume pricing note, tax info, installment form -->
  • Dynamic updates: Price updates via Section Rendering API when variants change.
  • Volume pricing: Shows note when quantity price breaks are configured.
  • Tax/duty info: Displays tax and duty information when applicable.
  • Installment form: Hidden form for payment terms display.

SKU Block ​

liquid
{%- when 'sku' -%}
  <p class="product__sku{% if selected_variant.sku.size == 0 %} visibility-hidden{% endif %}" id="sku-{{ section.id }}" {{ block.shopify_attributes }}>
    {{- selected_variant.sku -}}
  </p>
  • Conditional visibility: Hidden when variant has no SKU.

Variant Picker Block ​

liquid
{%- when 'variant_picker' -%}
  <div class="product__variants" {{ block.shopify_attributes }}>
    {% unless product.has_only_default_variant %}
      <variant-selector id="variant-selector-{{ section.id }}" data-picker-type="{{ block.settings.picker_type }}">
        <!-- Dropdown or button picker -->
      </variant-selector>
    {% endunless %}
  </div>
  • Two picker types: Dropdown (picker_type == 'dropdown') or button swatches (picker_type == 'button').
  • Variant selector: Custom element manages variant selection and triggers updates.

Quantity Selector Block ​

liquid
{%- when 'quantity_selector' -%}
  <div class="product__quantity" {{ block.shopify_attributes }}>
    <label class="option__label" for="Quantity-{{ section.id }}">Quantity</label>
    <quantity-selector>
      <!-- Minus button, input, plus button -->
    </quantity-selector>
  </div>
  • Quantity rules: Input respects variant quantity rules (min, max, increment).
  • Cart quantity: Shows current cart quantity for selected variant.

Description Block ​

liquid
{%- when 'description' -%}
  {%- if product.description != blank -%}
    <div class="product__description" {{ block.shopify_attributes }}>
      <p>{{ product.description }}</p>
    </div>
  {%- endif -%}

Buy Buttons Block ​

liquid
{%- when 'buy_buttons' -%}
  <div class="product-form__buttons" {{ block.shopify_attributes }}>
    {% form 'product', product, id: product_form_id, novalidate: 'novalidate' %}
      <div class="form-error"></div>
      <!-- Add to cart button, dynamic checkout -->
    {% endform %}
    
    <!-- Pickup availability (conditional) -->
  </div>
  • AJAX cart integration: The native cart engine (assets/cart.js) intercepts submits on any form[action*="/cart/add"] via event delegation — no wrapper element or data attributes needed. The submit button is disabled and its .loading__spinner shown while the request runs; errors render into the .form-error div.
  • Dynamic checkout: Optional Shop Pay, Apple Pay buttons.
  • Pickup availability: Optional local pickup information.

Custom Liquid Block ​

liquid
{%- when 'custom_liquid' -%}
  {{ block.settings.custom_liquid }}

Collapsible Tab Block ​

liquid
{%- when 'collapsible_tab' -%}
  <div class="product__accordion accordion" {{ block.shopify_attributes }}>
    <details>
      <summary>
        <!-- Icon, heading -->
      </summary>
      <div class="accordion__content">
        <!-- Content or page content -->
      </div>
    </details>
  </div>
  • Native details element: Uses semantic HTML5 <details> for accordion behavior.
  • Icon support: Optional icon display in accordion header.

Inventory Block ​

liquid
{%- when 'inventory' -%}
  <p class="product__inventory{% if selected_variant.inventory_management != 'shopify' %} visibility-hidden{% endif %}" id="inventory-{{ section.id }}" {{ block.shopify_attributes }}>
    <!-- Inventory status with icons and thresholds -->
  </p>
  • Inventory thresholds: Shows low stock warning when quantity is below threshold.
  • Color-coded icons: Different colors for in-stock, low-stock, out-of-stock states.

Share Block ​

liquid
{%- when 'share' -%}
  {% assign share_url = product.selected_variant.url | default: product.url | prepend: request.origin %}
  {% render 'component-product-share-button', block: block, share_link: share_url, section_id: section.id %}

Subscription Block ​

liquid
{%- when 'subscription' -%}
  <div class="product__subscription">
    <selling-plans-widget data-section-id="{{ section.id }}" {{ block.shopify_attributes }}>
      <!-- One-time purchase option, selling plan groups -->
    </selling-plans-widget>
  </div>
  • Selling plans: Displays subscription options with pricing adjustments.
  • Variant-specific plans: Shows plans for each variant with proper pricing.

Complementary Products Block ​

liquid
{%- when 'complementary' -%}
  <product-recommendations
    class="product-recommendations"
    data-url="{{ routes.product_recommendations_url }}?section_id={{ section.id }}&product_id={{ product.id }}&limit={{ block.settings.products_to_show }}&intent=complementary"
  >
    <!-- Product recommendations -->
  </product-recommendations>
  • AJAX loading: Recommendations loaded via AJAX by custom element.
  • Product cards: Uses component-product-card for consistent display.

Behavior ​

  • Variant selection: When variants are selected, the <product-info> custom element fetches updated HTML via the Section Rendering API and updates:

    • Product media gallery
    • Price, SKU, inventory
    • Add to cart button state
    • URL query parameters
  • Quantity rules: Quantity selector respects variant quantity rules (min, max, increment).

  • Sticky info: When enabled, product info column sticks to viewport on scroll.

  • Media gallery: Uses Swiper for carousel functionality with multiple layout options (carousel, 2 columns, thumbnail).

  • Image zoom: Supports lightbox, hover zoom, or no zoom based on settings.

  • AJAX cart: Add to cart submits are intercepted by the native cart engine (assets/cart.js), which posts the form data to /cart/add.js and dispatches a cart:change event on success (cart:error on failure).

  • Pickup availability: Fetches and displays local pickup information when enabled.


Schema ​

The section includes an extensive schema with multiple block types and settings. Key settings include:

Section Settings ​

  • Layout: media_size, media_position, media_fit, media_layout, mobile_media_layout
  • Image zoom: image_zoom (lightbox, hover, none)
  • Sticky info: enable_sticky_info
  • Color scheme: color_scheme
  • Padding: padding_top, padding_bottom
  • Variant labels: variant_label_color, variant_label_font_size, variant_label_font_weight

Block Settings ​

Each block type has its own settings for customization (colors, font sizes, text styles, etc.). See the schema section in the Liquid file for complete details.


Implementation Notes ​

  1. Custom element: The <product-info> custom element (defined in section-product.js) manages all variant state and coordinates updates via the Section Rendering API.

  2. Section Rendering API: When variants change, the section fetches updated HTML and selectively updates DOM elements (price, SKU, inventory, media, etc.) without full page reload.

  3. Media gallery: Uses component-product-media-gallery snippet which handles images, videos, and 3D models with Swiper carousel.

  4. Variant selector: The <variant-selector> custom element handles both dropdown and button-style pickers, triggering updates to the parent <product-info> element.

  5. Quantity selector: The <quantity-selector> custom element handles increment/decrement buttons and validates against variant quantity rules.

  6. AJAX cart: The product form is a plain {% form 'product' %}; the native cart engine (assets/cart.js) handles submission via document-level event delegation on form[action*="/cart/add"] for seamless cart updates.

  7. Pickup availability: Uses <pickup-availability> custom element which fetches store availability data and displays it in a drawer.

  8. Complementary products: Uses <product-recommendations> custom element which fetches recommendations via AJAX from Shopify's recommendation API.

  9. Selling plans: Complex subscription logic handled by <selling-plans-widget> custom element with variant-specific pricing.

  10. Translation keys: All user-facing text uses translation filters (e.g., t:sections.main-product.blocks.*).

  11. Conditional asset loading: Many CSS and JavaScript files are only loaded when specific blocks or settings are enabled to optimize page load.

  12. Responsive design: Mobile padding is 75% of desktop padding. Media layout can differ between desktop and mobile.

  13. Accessibility: Proper ARIA roles, labels, and semantic HTML throughout. Form inputs have proper labels and error handling.

  14. Icon dependencies: Multiple icons required: icon-spinner.svg, icon-unavailable.svg, icon-caret.svg, icon-inventory-status.svg.

  15. Product form ID: Generated as product-form-\{\{ section.id \}\} to ensure uniqueness when multiple product sections exist.

  16. Selected variant: Uses product.selected_or_first_available_variant as the default variant for initial display.

  17. Media modal: Optional lightbox modal for product images when image_zoom == 'lightbox'.

  18. Video looping: Optional video looping when enable_video_looping is enabled.

  19. Image magnify: Optional hover zoom functionality when image_zoom == 'hover'.

  20. Block limits: Some blocks have "limit": 1 in schema to prevent duplicates (title, price, variant_picker, etc.).