AgentSkillsCN

liquid-patterns

Shopify 主题开发中常用的 Liquid 代码模式。在编写 Liquid 模板、处理多语言翻译、展示产品信息或进行主题自定义时,可灵活运用这些模式。

SKILL.md
--- frontmatter
name: liquid-patterns
description: Common Liquid code patterns for Shopify theme development. Use when writing Liquid templates, handling translations, product displays, or theme customizations.

Shopify Liquid Patterns

Translation in Liquid

liquid
{% comment %} Use locale files for hardcoded text {% endcomment %}
{{ 'products.benefits.lifetime_guarantee' | t }}

{% comment %} With default fallback {% endcomment %}
{{ 'products.benefits.lifetime_description' | t: default: 'Fallback text' }}

{% comment %} Dynamic key translation {% endcomment %}
{% assign key = 'products.' | append: product.type | append: '.title' %}
{{ key | t }}

Product Display Patterns

Product Card

liquid
<div class="product-card" data-product-id="{{ product.id }}">
  {% if product.featured_image %}
    <img
      src="{{ product.featured_image | image_url: width: 400 }}"
      alt="{{ product.featured_image.alt | escape }}"
      loading="lazy"
      width="400"
      height="{{ 400 | divided_by: product.featured_image.aspect_ratio | round }}"
    >
  {% endif %}

  <h3>{{ product.title }}</h3>

  {% if product.compare_at_price > product.price %}
    <span class="price--sale">{{ product.price | money }}</span>
    <span class="price--compare">{{ product.compare_at_price | money }}</span>
  {% else %}
    <span class="price">{{ product.price | money }}</span>
  {% endif %}
</div>

Variant Selector (Dropdown)

liquid
{% for option in product.options_with_values %}
  <div class="option-selector">
    <label for="option-{{ forloop.index }}">{{ option.name }}</label>
    <select id="option-{{ forloop.index }}" name="options[{{ option.name }}]">
      {% for value in option.values %}
        <option
          value="{{ value }}"
          {% if option.selected_value == value %}selected{% endif %}
        >
          {{ value }}
        </option>
      {% endfor %}
    </select>
  </div>
{% endfor %}

Variant Selector (Radio Buttons with Availability)

liquid
{% for option in product.options_with_values %}
  <fieldset class="option">
    <legend>{{ option.name }}</legend>
    {% for option_value in option.values %}
      <input
        type="radio"
        id="option-{{ option.position }}-{{ forloop.index0 }}"
        name="{{ option.name }}"
        value="{{ option_value | escape }}"
        {% if option_value.selected %}checked{% endif %}
        {% unless option_value.available %}disabled{% endunless %}
      >
      <label for="option-{{ option.position }}-{{ forloop.index0 }}">
        {{ option_value }}
      </label>
    {% endfor %}
  </fieldset>
{% endfor %}

Image Handling

liquid
{% comment %} Use image_tag filter for automatic responsive images {% endcomment %}
{{ product.featured_image | image_url: width: 2000 | image_tag: loading: 'lazy' }}

{% comment %} Fixed-size image with retina support (1x/2x) {% endcomment %}
{% assign image = product.featured_image %}
<img
  src="{{ image | image_url: width: 600 }}"
  srcset="{{ image | image_url: width: 600 }} 1x,
          {{ image | image_url: width: 1200 }} 2x"
  alt="{{ image.alt | escape }}"
  loading="lazy"
  width="600"
  height="{{ 600 | divided_by: image.aspect_ratio | round }}"
>

{% comment %} Responsive srcset with retina support {% endcomment %}
{% assign image = product.featured_image %}
<img
  src="{{ image | image_url: width: 600 }}"
  srcset="{{ image | image_url: width: 300 }} 300w,
          {{ image | image_url: width: 600 }} 600w,
          {{ image | image_url: width: 900 }} 900w,
          {{ image | image_url: width: 1200 }} 1200w,
          {{ image | image_url: width: 1800 }} 1800w"
  sizes="(max-width: 600px) 300px, (max-width: 900px) 600px, 900px"
  alt="{{ image.alt | escape }}"
  loading="lazy"
>

Collection Loops

liquid
{% paginate collection.products by 12 %}
  <div class="product-grid">
    {% for product in collection.products %}
      {% render 'product-card', product: product %}
    {% endfor %}
  </div>

  {% if paginate.pages > 1 %}
    {{ paginate | default_pagination }}
  {% endif %}
{% endpaginate %}

Metafield Access

liquid
{% comment %} Product metafield {% endcomment %}
{% if product.metafields.custom.badge_type %}
  <span class="badge-type">{{ product.metafields.custom.badge_type.value }}</span>
{% endif %}

{% comment %} Shop metafield {% endcomment %}
{{ shop.metafields.global.announcement | metafield_tag }}

Conditional Rendering

liquid
{% comment %} Check for specific template {% endcomment %}
{% if template.name == 'product' %}
{% endif %}

{% comment %} Check customer status {% endcomment %}
{% if customer %}
  {{ 'account.welcome' | t: name: customer.first_name }}
{% else %}
  {{ 'account.login_prompt' | t }}
{% endif %}

Form Patterns

Add to Cart Form

liquid
{% form 'product', product %}
  <input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">

  <div class="quantity">
    <label for="quantity">{{ 'products.quantity' | t }}</label>
    <input type="number" id="quantity" name="quantity" value="1" min="1">
  </div>

  <button
    type="submit"
    {% unless product.available %}disabled{% endunless %}
  >
    {% if product.available %}
      {{ 'products.add_to_cart' | t }}
    {% else %}
      {{ 'products.sold_out' | t }}
    {% endif %}
  </button>
{% endform %}

Section Schema

liquid
{% schema %}
{
  "name": "Custom Section",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Section Heading"
    },
    {
      "type": "image_picker",
      "id": "image",
      "label": "Image"
    }
  ],
  "presets": [
    {
      "name": "Custom Section"
    }
  ]
}
{% endschema %}