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
| Type | Files / Components |
|---|---|
| CSS | swiper7.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) |
| JavaScript | section-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>, <ajax-cart-product-form>, <pickup-availability>, <product-recommendations>, <selling-plans-widget> |
| Snippets | component-product-media-gallery, component-product-price, component-product-share-button, component-product-card, component-pickup-availability, component-product-media-modal |
| Icons | icon-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:
{%- 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
<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_positionsetting. - 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
{%- 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
{%- when 'title' -%}
<div class="product__title" {{ block.shopify_attributes }}>
<h1>{{ product.title }}</h1>
</div>Price Block
{%- 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
{%- 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
{%- 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
{%- 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
{%- when 'description' -%}
{%- if product.description != blank -%}
<div class="product__description" {{ block.shopify_attributes }}>
<p>{{ product.description }}</p>
</div>
{%- endif -%}Buy Buttons Block
{%- when 'buy_buttons' -%}
<div class="product-form__buttons" {{ block.shopify_attributes }}>
<ajax-cart-product-form>
{% form 'product', product, id: product_form_id, novalidate: 'novalidate' %}
<!-- Add to cart button, dynamic checkout -->
{% endform %}
</ajax-cart-product-form>
<!-- Pickup availability (conditional) -->
</div>- AJAX cart integration: Uses
ajax-cart-product-formcustom element for cart updates. - Dynamic checkout: Optional Shop Pay, Apple Pay buttons.
- Pickup availability: Optional local pickup information.
Custom Liquid Block
{%- when 'custom_liquid' -%}
{{ block.settings.custom_liquid }}Collapsible Tab Block
{%- 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
{%- 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
{%- 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
{%- 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
{%- 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-cardfor 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 uses AJAX via
ajax-cart-product-formcustom element.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
Custom element: The
<product-info>custom element (defined insection-product.js) manages all variant state and coordinates updates via the Section Rendering API.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.
Media gallery: Uses
component-product-media-gallerysnippet which handles images, videos, and 3D models with Swiper carousel.Variant selector: The
<variant-selector>custom element handles both dropdown and button-style pickers, triggering updates to the parent<product-info>element.Quantity selector: The
<quantity-selector>custom element handles increment/decrement buttons and validates against variant quantity rules.AJAX cart: Uses
ajax-cart-product-formcustom element (likely from Liquid Ajax Cart library) for seamless cart updates.Pickup availability: Uses
<pickup-availability>custom element which fetches store availability data and displays it in a drawer.Complementary products: Uses
<product-recommendations>custom element which fetches recommendations via AJAX from Shopify's recommendation API.Selling plans: Complex subscription logic handled by
<selling-plans-widget>custom element with variant-specific pricing.Translation keys: All user-facing text uses translation filters (e.g.,
t:sections.main-product.blocks.*).Conditional asset loading: Many CSS and JavaScript files are only loaded when specific blocks or settings are enabled to optimize page load.
Responsive design: Mobile padding is 75% of desktop padding. Media layout can differ between desktop and mobile.
Accessibility: Proper ARIA roles, labels, and semantic HTML throughout. Form inputs have proper labels and error handling.
Icon dependencies: Multiple icons required:
icon-spinner.svg,icon-unavailable.svg,icon-caret.svg,icon-inventory-status.svg.Product form ID: Generated as
product-form-\{\{ section.id \}\}to ensure uniqueness when multiple product sections exist.Selected variant: Uses
product.selected_or_first_available_variantas the default variant for initial display.Media modal: Optional lightbox modal for product images when
image_zoom == 'lightbox'.Video looping: Optional video looping when
enable_video_loopingis enabled.Image magnify: Optional hover zoom functionality when
image_zoom == 'hover'.Block limits: Some blocks have
"limit": 1in schema to prevent duplicates (title, price, variant_picker, etc.).