← Shopify App BuilderCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Shopify App Builder
Snapshot Sep 30, 2026 · 23:13 UTC · version 1.4.1
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Use this skill for Liquid Theme Development (Online Store 2.0). Triggers include: 'liquid theme development', 'shopify theme online store 2.0', 'liquid section schema', 'liquid blocks', 'liquid filters', 'liquid objects', 'json template shopify', 'theme preset', 'liquid include snippet', 'theme-check linting', 'liquid forloop iteration', 'liquid if conditions', 'liquid assign variable', 'shopify theme app extension', 'liquid capture variable', 'theme metafields', 'liquid date filter', 'liquid money filter', 'liquid array filters', 'liquid string manipulation', 'liquid product page', 'liquid collection page', 'liquid cart page', 'liquid header footer', 'liquid for loop break continue', 'liquid unless statement', 'liquid case when'.",
"included_files": [],
"name": "liquid-themes",
"skill_md_contents": "---\nname: liquid-themes\ndescription: \"Use this skill for Liquid Theme Development (Online Store 2.0). Triggers include: 'liquid theme development', 'shopify theme online store 2.0', 'liquid section schema', 'liquid blocks', 'liquid filters', 'liquid objects', 'json template shopify', 'theme preset', 'liquid include snippet', 'theme-check linting', 'liquid forloop iteration', 'liquid if conditions', 'liquid assign variable', 'shopify theme app extension', 'liquid capture variable', 'theme metafields', 'liquid date filter', 'liquid money filter', 'liquid array filters', 'liquid string manipulation', 'liquid product page', 'liquid collection page', 'liquid cart page', 'liquid header footer', 'liquid for loop break continue', 'liquid unless statement', 'liquid case when'.\"\n---\n\n# Liquid Theme Development (Online Store 2.0)\n\nLiquid is Shopify's templating language for building themes and sections on Online Store 2.0. This skill covers Liquid syntax, section schema definition, JSON templates, blocks, filters (20+ common), objects, theme file structure, presets, and real-world section and template patterns.\n\n## When Asked\n\n**When asked to build a Liquid section with settings:**\nProvide a complete section file with schema (settings array, presets, locales), CSS, and JavaScript; explain how settings map to template variables.\n\n**Before returning any section, app block, or theme schema:**\nRun the Shopify schema dedupe check mentally or with `scripts/validate-shopify-schema-ids.mjs <theme-or-file>`. Every `settings[].id` must be unique within its own schema scope, every block's `settings[].id` must be unique inside that block type, and every `blocks[].type` should be unique unless you are intentionally defining different block entries. Do not paste settings from multiple examples until you merge and dedupe IDs.\n\n**When asked to implement product filters or sorting:**\nUse the collection product pagination, filters by attribute, sorting with Liquid forloop and if/case statements; explain faceting.\n\n**When asked to add custom fields to products:**\nUse metafields in Liquid (product.metafields.namespace.key), explain metafield definitions, and show rendering in sections.\n\n**When asked to create reusable components:**\nUse include/render snippets with passed parameters; explain snippet vs. render differences and variable scope.\n\n**When asked about Liquid filters and string manipulation:**\nProvide examples of 20+ filters (split, join, size, capitalize, downcase, strip_html, truncate, money, date, etc.) with use cases.\n\n**When asked to optimize theme performance:**\nDiscuss lazy loading, critical CSS, reducing render-blocking resources, and using theme-check for linting.\n\n---\n\n## Liquid & Online Store 2.0 Architecture\n\nLiquid provides:\n- **Section-Based Editing**: Drag-and-drop sections with live customization\n- **Schema System**: Settings (text, select, checkbox, range, color), preset configurations\n- **Blocks**: Repeatable content blocks within sections (e.g., carousel slides, testimonials)\n- **JSON Templates**: Structured page templates with sections array\n- **Filters**: 50+ filters for formatting (string, date, money, array, math)\n- **Objects**: Global objects (shop, page, product, collection, cart, customer)\n- **Include & Render**: Reusable snippets with parameter passing\n- **Forloop & Control**: for, if, unless, case/when, break, continue statements\n- **Metafields**: Custom product/collection/order data fields\n\n### Schema Dedupe Guardrail\n\nShopify section schemas use arrays, so JSON itself will not protect you from duplicated visible inputs. A duplicated setting object with the same `id` can make Theme Editor show repeated controls or make generated Liquid read the wrong setting.\n\nHard rules before emitting schema:\n\n- Keep section-level `settings[].id` unique.\n- Keep `blocks[].type` unique within a section or app block.\n- Keep each block's `settings[].id` unique inside that block.\n- In `config/settings_schema.json`, keep setting IDs unique across theme setting groups unless Shopify's own template intentionally scopes them.\n- If you copy a \"padding\", \"heading\", \"text\", \"image\", \"color\", or \"button_label\" setting from another example, rename it by purpose: `hero_padding`, `card_padding`, `button_label_primary`, not another generic `padding`.\n- Preset `settings` keys must match real setting IDs. Delete stale preset keys after renaming.\n- After generation, run:\n\n```bash\nnode <plugin-root>/scripts/validate-shopify-schema-ids.mjs path/to/theme-or-section.liquid\n```\n\nTreat any duplicate as a blocker before handoff.\n\n### Theme Directory Structure\n\n```\ntheme/\n├── assets/ # CSS, JS, images, fonts\n│ ├── base.css\n│ ├── custom.js\n│ └── logo.png\n├── config/\n│ ├── settings_schema.json # Theme-wide settings\n│ └── settings_data.json # Store settings values\n├── layout/\n│ ├── theme.liquid # Root layout\n│ └── password.liquid # Password-protected layout\n├── sections/ # Customizable sections\n│ ├── product.liquid\n│ ├── collection.liquid\n│ ├── hero.liquid\n│ └── newsletter.liquid\n├── snippets/ # Reusable components\n│ ├── product-card.liquid\n│ ├── breadcrumbs.liquid\n│ └── pagination.liquid\n├── templates/ # Page templates (JSON)\n│ ├── product.json\n│ ├── collection.json\n│ ├── index.json\n│ ├── page.json\n│ ├── cart.json\n│ └── 404.json\n└── locales/\n ├── en.json # English translations\n └── fr.json # French translations\n```\n\n---\n\n## Liquid Syntax Fundamentals\n\n### Variables & Output\n\n```liquid\n{{ product.title }}\n{{ product.price | money }}\n{{ 'Hello World' | upcase }}\n{{ section.settings.text_field }}\n```\n\n### Assign & Capture\n\n```liquid\n{% assign name = 'John' %}\nHello {{ name }}!\n\n{% assign price_times_two = product.price | times: 2 %}\n\n{% capture my_variable %}\n I am being captured.\n{% endcapture %}\n{{ my_variable }}\n```\n\n### Forloop\n\n```liquid\n{% for product in collection.products %}\n <div class=\"product-card\">\n <h3>{{ product.title }}</h3>\n <p>${{ product.price | money }}</p>\n </div>\n{% endfor %}\n\n{% for i in (1..5) %}\n Item {{ i }}\n{% endfor %}\n\n{% for item in array %}\n {% if forloop.first %}\n <p>First item: {{ item }}</p>\n {% elsif forloop.last %}\n <p>Last item: {{ item }}</p>\n {% else %}\n <p>Item {{ forloop.index }} of {{ forloop.length }}: {{ item }}</p>\n {% endif %}\n{% endfor %}\n\n{% for item in array limit: 5 %}\n {{ item }}\n{% endfor %}\n\n{% for item in array offset: 10 %}\n Item number {{ forloop.index }}\n{% endfor %}\n```\n\n**Forloop Properties:**\n- `forloop.index` — 1-based position\n- `forloop.index0` — 0-based position\n- `forloop.first`, `forloop.last` — boolean\n- `forloop.length` — total items\n- `forloop.rindex` — reverse index\n\n### Conditionals\n\n```liquid\n{% if customer %}\n Hello, {{ customer.first_name }}!\n{% elsif customer.email %}\n Hello, {{ customer.email }}!\n{% else %}\n Hello, guest!\n{% endif %}\n\n{% unless product.available %}\n <p>Out of stock</p>\n{% endunless %}\n\n{% case handle %}\n {% when 'electronics' %}\n <p>Electronics category</p>\n {% when 'clothing' %}\n <p>Clothing category</p>\n {% else %}\n <p>Other category</p>\n{% endcase %}\n\n{% if product.available and product.price > 100 %}\n Premium item in stock\n{% endif %}\n\n{% if product.available or customer %}\n Available or customer logged in\n{% endif %}\n```\n\n---\n\n## Filters (20+ Common)\n\n### String Filters\n\n```liquid\n{{ 'hello world' | upcase }} # HELLO WORLD\n{{ 'HELLO' | downcase }} # hello\n{{ 'hello' | capitalize }} # Hello\n{{ 'hello world' | replace: 'world', 'liquid' }} # hello liquid\n{{ 'hello world' | split: ' ' | join: '-' }} # hello-world\n{{ 'hello world' | slice: 0, 5 }} # hello\n{{ product.title | truncate: 20, '...' }} # Truncates to 20 chars with ellipsis\n{{ '<p>hello</p>' | strip_html }} # hello\n```\n\n### Math Filters\n\n```liquid\n{{ 16 | plus: 4 }} # 20\n{{ 16 | minus: 4 }} # 12\n{{ 4 | times: 5 }} # 20\n{{ 16 | divided_by: 4 }} # 4\n{{ 5.4 | ceil }} # 6\n{{ 5.4 | floor }} # 5\n{{ 5.4 | round }} # 5\n{{ 4 | modulo: 2 }} # 0\n```\n\n### Money Filter\n\n```liquid\n{{ product.price | money }} # $29.99 (formatted per shop currency)\n{{ product.price | money_with_currency }} # $29.99 USD\n{{ 10 | times: product.price | money }} # $299.90\n```\n\n### Array Filters\n\n```liquid\n{{ array | size }} # Length of array\n{{ array | first }} # First element\n{{ array | last }} # Last element\n{{ array | join: ', ' }} # 'item1, item2, item3'\n{{ array | reverse | join: ', ' }} # Reverse order\n{{ array | sort | join: ', ' }} # Sort array\n{{ array | uniq | join: ', ' }} # Remove duplicates\n{{ array | map: 'title' | join: ', '}} # Extract 'title' from each item\n{% assign sorted = collection.products | sort: 'price' %}\n```\n\n### Date Filters\n\n```liquid\n{{ 'now' | date: '%Y-%m-%d' }} # 2024-05-04\n{{ product.created_at | date: '%B %d, %Y' }} # May 04, 2024\n{{ order.created_at | date: '%I:%M %p' }} # 03:45 PM\n```\n\n---\n\n## Liquid Objects\n\n### Shop Object\n\n```liquid\n{{ shop.name }} # Store name\n{{ shop.currency }} # USD\n{{ shop.url }} # mystore.myshopify.com\n{{ shop.email }} # contact@mystore.com\n{{ shop.phone }} # +1-800-123-4567\n{{ shop.customer_accounts_enabled }} # true/false\n```\n\n### Product Object\n\n```liquid\n{{ product.id }} # Shopify product ID\n{{ product.title }} # Product name\n{{ product.description }} # Product description\n{{ product.price }} # Price (in cents)\n{{ product.price | money }} # Formatted: $29.99\n{{ product.compare_at_price | money }} # Original price\n{{ product.available }} # true/false\n{{ product.variants.size }} # Number of variants\n{{ product.featured_image.src }} # Image URL\n{{ product.handle }} # URL slug\n{{ product.type }} # Product type\n{{ product.vendor }} # Brand/vendor\n{{ product.tags | join: ', '}} # CSV tags\n\n{% for variant in product.variants %}\n <p>{{ variant.title }} - {{ variant.price | money }}</p>\n{% endfor %}\n```\n\n### Collection Object\n\n```liquid\n{{ collection.id }}\n{{ collection.title }}\n{{ collection.url }}\n{{ collection.description }}\n{{ collection.image.src }}\n{{ collection.products_count }}\n{{ collection.handle }}\n\n{% for product in collection.products %}\n {% include 'product-card', product: product %}\n{% endfor %}\n\n{% if collection.previous_product %}\n <a href=\"{{ collection.previous_product.url }}\">← {{ collection.previous_product.title }}</a>\n{% endif %}\n\n{% if collection.next_product %}\n <a href=\"{{ collection.next_product.url }}\">{{ collection.next_product.title }} →</a>\n{% endif %}\n```\n\n### Cart Object\n\n```liquid\n{{ cart.item_count }} # Total items in cart\n{{ cart.total_price | money }} # Cart subtotal\n{{ cart.total_price | money_with_currency }}\n\n{% for item in cart.items %}\n <p>{{ item.title }} x {{ item.quantity }} = {{ item.final_line_price | money }}</p>\n{% endfor %}\n\n{{ cart.note }} # Cart note/comments\n```\n\n### Customer Object\n\n```liquid\n{% if customer %}\n Hello, {{ customer.first_name }}!\n Email: {{ customer.email }}\n Phone: {{ customer.phone }}\n {{ customer.orders_count }} orders\n Loyal since: {{ customer.created_at | date: '%B %Y' }}\n{% endif %}\n```\n\n---\n\n## Section Schema & Settings\n\n### Basic Section with Settings\n\n```liquid\n{%- stylesheet %}\n .section-hero {\n background: {{ section.settings.bg_color }};\n padding: {{ section.settings.padding }}px;\n }\n .hero-title {\n color: {{ section.settings.title_color }};\n font-size: {{ section.settings.title_size }}px;\n }\n{%- endstylesheet %}\n\n<section class=\"section-hero\">\n <h1 class=\"hero-title\">{{ section.settings.heading }}</h1>\n <p>{{ section.settings.description }}</p>\n {% if section.settings.show_button %}\n <a href=\"{{ section.settings.button_link }}\" class=\"button\">\n {{ section.settings.button_text }}\n </a>\n {% endif %}\n</section>\n\n{% schema %}\n{\n \"name\": \"Hero Banner\",\n \"settings\": [\n {\n \"type\": \"text\",\n \"id\": \"heading\",\n \"label\": \"Heading\",\n \"default\": \"Welcome to our store\"\n },\n {\n \"type\": \"textarea\",\n \"id\": \"description\",\n \"label\": \"Description\",\n \"default\": \"Share your store's story\"\n },\n {\n \"type\": \"color\",\n \"id\": \"bg_color\",\n \"label\": \"Background Color\",\n \"default\": \"#ffffff\"\n },\n {\n \"type\": \"color\",\n \"id\": \"title_color\",\n \"label\": \"Title Color\",\n \"default\": \"#000000\"\n },\n {\n \"type\": \"range\",\n \"id\": \"padding\",\n \"min\": 0,\n \"max\": 100,\n \"step\": 5,\n \"label\": \"Padding (px)\",\n \"default\": 40\n },\n {\n \"type\": \"range\",\n \"id\": \"title_size\",\n \"min\": 24,\n \"max\": 72,\n \"label\": \"Title Size (px)\",\n \"default\": 48\n },\n {\n \"type\": \"checkbox\",\n \"id\": \"show_button\",\n \"label\": \"Show Button\",\n \"default\": true\n },\n {\n \"type\": \"text\",\n \"id\": \"button_text\",\n \"label\": \"Button Text\",\n \"default\": \"Shop Now\"\n },\n {\n \"type\": \"url\",\n \"id\": \"button_link\",\n \"label\": \"Button Link\"\n },\n {\n \"type\": \"select\",\n \"id\": \"alignment\",\n \"label\": \"Alignment\",\n \"options\": [\n { \"value\": \"left\", \"label\": \"Left\" },\n { \"value\": \"center\", \"label\": \"Center\" },\n { \"value\": \"right\", \"label\": \"Right\" }\n ],\n \"default\": \"center\"\n }\n ]\n}\n{% endschema %}\n```\n\n### Section with Blocks\n\n```liquid\n<section class=\"testimonials\">\n <h2>What Our Customers Say</h2>\n <div class=\"testimonials-grid\">\n {%- for block in section.blocks -%}\n <div class=\"testimonial\" {{ block.shopify_attributes }}>\n <p class=\"quote\">{{ block.settings.quote }}</p>\n <p class=\"author\">— {{ block.settings.author }}</p>\n <div class=\"rating\">\n {%- for i in (1..block.settings.stars) -%}\n ⭐\n {%- endfor -%}\n </div>\n </div>\n {%- endfor -%}\n </div>\n</section>\n\n{% schema %}\n{\n \"name\": \"Testimonials\",\n \"blocks\": [\n {\n \"type\": \"testimonial\",\n \"name\": \"Testimonial\",\n \"settings\": [\n {\n \"type\": \"textarea\",\n \"id\": \"quote\",\n \"label\": \"Quote\",\n \"default\": \"Great product!\"\n },\n {\n \"type\": \"text\",\n \"id\": \"author\",\n \"label\": \"Author\",\n \"default\": \"John Smith\"\n },\n {\n \"type\": \"range\",\n \"id\": \"stars\",\n \"min\": 1,\n \"max\": 5,\n \"label\": \"Rating\",\n \"default\": 5\n }\n ]\n }\n ]\n}\n{% endschema %}\n```\n\n### Presets (Default Configuration)\n\n```liquid\n{% schema %}\n{\n \"name\": \"Featured Product\",\n \"settings\": [...],\n \"presets\": [\n {\n \"name\": \"Featured Product\",\n \"settings\": {\n \"product\": \"gid://shopify/Product/123456\",\n \"show_rating\": true,\n \"show_reviews\": true\n }\n },\n {\n \"name\": \"Featured Product - Minimal\",\n \"settings\": {\n \"show_rating\": false,\n \"show_reviews\": false\n }\n }\n ]\n}\n{% endschema %}\n```\n\n---\n\n## Snippets & Reusable Components\n\n### Include vs. Render\n\n```liquid\n{%- include 'product-card', product: product -%}\n```\n\nvs.\n\n```liquid\n{%- render 'product-card', product: product -%}\n```\n\n**Include:** Shares scope with parent template (slower, backwards-compat)\n**Render:** Isolated scope, faster, recommended for components\n\n### Product Card Snippet\n\n```liquid\n{%- comment -%}\n Reusable product card component\n Usage: {% render 'product-card', product: product, show_price: true %}\n{%- endcomment -%}\n\n<div class=\"product-card\">\n <a href=\"{{ product.url }}\" class=\"product-image\">\n {% if product.featured_image %}\n <img\n src=\"{{ product.featured_image.src | img_url: '300x300' }}\"\n alt=\"{{ product.featured_image.alt }}\"\n loading=\"lazy\"\n />\n {% endif %}\n </a>\n\n <h3 class=\"product-title\">\n <a href=\"{{ product.url }}\">{{ product.title }}</a>\n </h3>\n\n <p class=\"product-vendor\">{{ product.vendor }}</p>\n\n {% if show_price %}\n <div class=\"product-price\">\n {% if product.compare_at_price > product.price %}\n <span class=\"original-price\">{{ product.compare_at_price | money }}</span>\n <span class=\"sale-price\">{{ product.price | money }}</span>\n {% else %}\n <span class=\"price\">{{ product.price | money }}</span>\n {% endif %}\n </div>\n {% endif %}\n\n {% if product.available %}\n <button class=\"btn-add-to-cart\" data-product-id=\"{{ product.id }}\">\n Add to cart\n </button>\n {% else %}\n <p class=\"out-of-stock\">Out of Stock</p>\n {% endif %}\n</div>\n\n<style>\n .product-card {\n border: 1px solid #ddd;\n padding: 16px;\n border-radius: 8px;\n }\n .product-title a {\n text-decoration: none;\n color: #000;\n }\n</style>\n\n<script>\n document.querySelectorAll('.btn-add-to-cart').forEach(btn => {\n btn.addEventListener('click', function() {\n const productId = this.dataset.productId;\n fetch('/cart/add.js', {\n method: 'POST',\n body: JSON.stringify({ id: productId, quantity: 1 })\n });\n });\n });\n</script>\n```\n\n---\n\n## JSON Templates\n\n### Product Page Template\n\n```json\n{\n \"sections\": {\n \"main\": {\n \"type\": \"product\",\n \"settings\": {\n \"enable_sticky_add_to_cart\": true,\n \"gallery_layout\": \"thumbnail\",\n \"media_size\": \"medium\",\n \"image_zoom\": true,\n \"show_vendor\": true,\n \"show_sku\": true,\n \"enable_video_looping\": false\n }\n },\n \"product-recommendations\": {\n \"type\": \"product-recommendations\",\n \"settings\": {\n \"heading\": \"You might also like\",\n \"products_per_row\": 4\n }\n }\n },\n \"order\": [\"main\", \"product-recommendations\"]\n}\n```\n\n### Collection Page Template\n\n```json\n{\n \"sections\": {\n \"collection-header\": {\n \"type\": \"collection-header\",\n \"settings\": {\n \"show_image\": true,\n \"show_description\": true\n }\n },\n \"collection-filters\": {\n \"type\": \"collection-filters\",\n \"settings\": {\n \"enable_filters\": true,\n \"enable_sorting\": true\n }\n },\n \"collection-products\": {\n \"type\": \"collection-products\",\n \"settings\": {\n \"products_per_page\": 12,\n \"columns_desktop\": 4,\n \"columns_mobile\": 2\n }\n }\n },\n \"order\": [\"collection-header\", \"collection-filters\", \"collection-products\"]\n}\n```\n\n---\n\n## Metafields in Liquid\n\n### Displaying Metafields\n\n```liquid\n{%- if product.metafields.custom.care_instructions -%}\n <div class=\"care-instructions\">\n <h3>Care Instructions</h3>\n <p>{{ product.metafields.custom.care_instructions.value }}</p>\n </div>\n{%- endif -%}\n\n{%- if product.metafields.custom.size_chart -%}\n <img src=\"{{ product.metafields.custom.size_chart.value }}\" alt=\"Size Chart\" />\n{%- endif -%}\n\n{%- for item in product.metafields.custom.ingredients.value -%}\n <li>{{ item }}</li>\n{%- endfor -%}\n```\n\n### Defining Metafields (in Admin or via API)\n\n```\nNamespace: custom\nKey: care_instructions\nType: single_line_text_field\n\nNamespace: custom\nKey: ingredients\nType: list.single_line_text_field\n```\n\n---\n\n## Worked Example: Product Section\n\n```liquid\n<section class=\"section-product\" id=\"product-{{ section.id }}\">\n <div class=\"product-container\">\n <div class=\"product-gallery\">\n {%- for image in product.images -%}\n <img\n src=\"{{ image.src | img_url: '500x500' }}\"\n alt=\"{{ image.alt }}\"\n loading=\"{{ 'lazy' if forloop.index > 1 else 'eager' }}\"\n class=\"product-image\"\n />\n {%- endfor -%}\n </div>\n\n <div class=\"product-info\">\n <h1 class=\"product-title\">{{ product.title }}</h1>\n\n {% if section.settings.show_vendor %}\n <p class=\"product-vendor\">{{ product.vendor }}</p>\n {% endif %}\n\n <div class=\"product-price\">\n {% if product.compare_at_price > product.price %}\n <span class=\"original-price\">{{ product.compare_at_price | money }}</span>\n <span class=\"sale-badge\">Sale</span>\n {% endif %}\n <span class=\"price\">{{ product.price | money }}</span>\n </div>\n\n <p class=\"product-description\">{{ product.description }}</p>\n\n <form action=\"/cart/add\" method=\"post\">\n {%- for option in product.options -%}\n <fieldset>\n <legend>{{ option.name }}</legend>\n <select name=\"options[{{ option.name }}]\" required>\n {%- for value in option.values -%}\n <option value=\"{{ value }}\">{{ value }}</option>\n {%- endfor -%}\n </select>\n </fieldset>\n {%- endfor -%}\n\n <input type=\"hidden\" name=\"id\" value=\"{{ product.variants.first.id }}\" />\n\n <input\n type=\"number\"\n name=\"quantity\"\n value=\"1\"\n min=\"1\"\n max=\"{{ product.selected_or_first_available_variant.inventory_quantity }}\"\n class=\"quantity-input\"\n />\n\n <button type=\"submit\" class=\"btn-add-to-cart\">\n {% if product.available %}\n Add to cart\n {% else %}\n Out of stock\n {% endif %}\n </button>\n </form>\n\n {% if section.settings.show_reviews %}\n <div class=\"product-reviews\">\n <p>⭐⭐⭐⭐⭐ ({{ product.reviews_count }} reviews)</p>\n </div>\n {% endif %}\n </div>\n </div>\n</section>\n\n{%- stylesheet %}\n .section-product {\n padding: {{ section.settings.padding }}px;\n }\n .product-container {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 40px;\n }\n .product-price {\n font-size: 20px;\n font-weight: bold;\n }\n .sale-badge {\n background: red;\n color: white;\n padding: 4px 8px;\n border-radius: 4px;\n margin-left: 8px;\n }\n @media (max-width: 768px) {\n .product-container {\n grid-template-columns: 1fr;\n }\n }\n{%- endstylesheet %}\n\n{% schema %}\n{\n \"name\": \"Product\",\n \"settings\": [\n {\n \"type\": \"checkbox\",\n \"id\": \"show_vendor\",\n \"label\": \"Show vendor\",\n \"default\": true\n },\n {\n \"type\": \"checkbox\",\n \"id\": \"show_reviews\",\n \"label\": \"Show reviews\",\n \"default\": true\n },\n {\n \"type\": \"range\",\n \"id\": \"padding\",\n \"min\": 0,\n \"max\": 100,\n \"step\": 5,\n \"label\": \"Padding (px)\",\n \"default\": 40\n }\n ]\n}\n{% endschema %}\n```\n\n---\n\n## Theme Linting with theme-check\n\n```bash\nnpm install -g @shopify/theme-check\ntheme-check --help\ntheme-check . # Lint entire theme\n```\n\nCommon rules:\n- Unused snippets\n- Missing alt text on images\n- Deprecated Liquid tags\n- Missing schema translations\n- Performance issues (large assets, render-blocking)\n\n---\n\n## Performance & Optimization Tips\n\n- Use `{{ image.src | img_url: '300x300' }}` for responsive images\n- Set `loading=\"lazy\"` on below-fold images\n- Minimize CSS in sections (use scoped `{% stylesheet %}`)\n- Defer non-critical JS with `defer` attribute\n- Use `capture` and `assign` to avoid multiple API calls\n- Lazy load sections with `{% section 'footer' %}`\n- Cache computed values: `{% assign sorted = array | sort %}`\n- Use `unless` instead of `{% if not %}` for readability\n- Limit forloop iterations with `limit` and `offset`\n- Use theme-check to identify performance issues\n\n---\n\n## Common Patterns Checklist\n\n- [ ] Define section schema with at least 3 settings\n- [ ] Use `section.settings` to access customizable values\n- [ ] Include blocks for repeating content (testimonials, gallery)\n- [ ] Use `render` instead of `include` for components\n- [ ] Pass parameters to snippets: `render 'card', item: product`\n- [ ] Add `.shopify_attributes` to block divs for editor selection\n- [ ] Use filters for formatting (money, date, string manipulation)\n- [ ] Check product.available before showing \"Add to cart\"\n- [ ] Use `img_url` filter for responsive image sizing\n- [ ] Add `loading=\"lazy\"` to below-fold images\n- [ ] Define presets with sensible defaults\n- [ ] Add translations (JSON files in locales/)\n- [ ] Use theme-check to lint for errors\n- [ ] Test with product variants, multiple images, long titles\n"
}SHA-256 of public snapshot: db37984d943400d52daeeb368c175f7002481a871520bb38536c9ebe7f06ada6