Initial commit
This commit is contained in:
453
skills/shopify-i18n-basics/SKILL.md
Normal file
453
skills/shopify-i18n-basics/SKILL.md
Normal file
@@ -0,0 +1,453 @@
|
||||
---
|
||||
name: Shopify i18n Basics
|
||||
description: Internationalization fundamentals for multi-language Shopify themes
|
||||
---
|
||||
|
||||
# Shopify i18n Basics
|
||||
|
||||
## Locale File Structure
|
||||
|
||||
Shopify themes use JSON files in the `locales/` directory:
|
||||
|
||||
```
|
||||
locales/
|
||||
├── en.default.json # English (default language)
|
||||
├── en.default.schema.json # English schema translations
|
||||
├── fr.json # French translations
|
||||
├── fr.schema.json # French schema translations
|
||||
├── de.json # German translations
|
||||
└── de.schema.json # German schema translations
|
||||
```
|
||||
|
||||
## File Types
|
||||
|
||||
### 1. Content Translations (en.default.json)
|
||||
|
||||
Used for theme content displayed to customers:
|
||||
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"search": "Search",
|
||||
"cart": "Cart",
|
||||
"menu": "Menu",
|
||||
"close": "Close",
|
||||
"loading": "Loading...",
|
||||
"continue_shopping": "Continue Shopping"
|
||||
},
|
||||
"product": {
|
||||
"add_to_cart": "Add to Cart",
|
||||
"sold_out": "Sold Out",
|
||||
"unavailable": "Unavailable",
|
||||
"price": "Price",
|
||||
"vendor": "Vendor"
|
||||
},
|
||||
"cart": {
|
||||
"title": "Your Cart",
|
||||
"empty": "Your cart is empty",
|
||||
"subtotal": "Subtotal",
|
||||
"checkout": "Checkout"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Schema Translations (en.default.schema.json)
|
||||
|
||||
Used for Theme Editor labels:
|
||||
|
||||
```json
|
||||
{
|
||||
"sections": {
|
||||
"featured_products": {
|
||||
"name": "Featured Products",
|
||||
"settings": {
|
||||
"heading": {
|
||||
"label": "Heading",
|
||||
"info": "Section heading text"
|
||||
},
|
||||
"collection": {
|
||||
"label": "Collection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Using Translations in Liquid
|
||||
|
||||
### Basic Translation
|
||||
```liquid
|
||||
{{ 'general.search' | t }}
|
||||
{{ 'product.add_to_cart' | t }}
|
||||
{{ 'cart.title' | t }}
|
||||
```
|
||||
|
||||
### Translation with Variables
|
||||
```liquid
|
||||
{%- # In locale file -%}
|
||||
{
|
||||
"cart": {
|
||||
"item_count": "{{ count }} items"
|
||||
}
|
||||
}
|
||||
|
||||
{%- # In Liquid -%}
|
||||
{{ 'cart.item_count' | t: count: cart.item_count }}
|
||||
```
|
||||
|
||||
### Translation with HTML
|
||||
```liquid
|
||||
{%- # In locale file -%}
|
||||
{
|
||||
"general": {
|
||||
"continue_html": "Continue <span>shopping</span>"
|
||||
}
|
||||
}
|
||||
|
||||
{%- # In Liquid -%}
|
||||
{{ 'general.continue_html' | t }}
|
||||
```
|
||||
|
||||
### Translation in Schema
|
||||
```liquid
|
||||
{% schema %}
|
||||
{
|
||||
"name": "t:sections.featured_products.name",
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "heading",
|
||||
"label": "t:sections.featured_products.settings.heading.label"
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endschema %}
|
||||
```
|
||||
|
||||
## Common Translation Patterns
|
||||
|
||||
### General UI
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"search": "Search",
|
||||
"cart": "Cart",
|
||||
"menu": "Menu",
|
||||
"close": "Close",
|
||||
"loading": "Loading...",
|
||||
"buttons": {
|
||||
"learn_more": "Learn More",
|
||||
"shop_now": "Shop Now",
|
||||
"view_all": "View All"
|
||||
},
|
||||
"forms": {
|
||||
"submit": "Submit",
|
||||
"email": "Email",
|
||||
"name": "Name",
|
||||
"message": "Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Product-Specific
|
||||
```json
|
||||
{
|
||||
"product": {
|
||||
"add_to_cart": "Add to Cart",
|
||||
"sold_out": "Sold Out",
|
||||
"unavailable": "Unavailable",
|
||||
"in_stock": "In Stock",
|
||||
"out_of_stock": "Out of Stock",
|
||||
"price": {
|
||||
"from": "From {{ price }}",
|
||||
"regular_price": "Regular price",
|
||||
"sale_price": "Sale price"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cart & Checkout
|
||||
```json
|
||||
{
|
||||
"cart": {
|
||||
"title": "Cart",
|
||||
"empty": "Your cart is empty",
|
||||
"item_count": "{{ count }} items",
|
||||
"subtotal": "Subtotal",
|
||||
"shipping": "Shipping",
|
||||
"total": "Total",
|
||||
"checkout": "Checkout",
|
||||
"remove": "Remove"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Forms & Errors
|
||||
```json
|
||||
{
|
||||
"forms": {
|
||||
"contact": {
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"message": "Message",
|
||||
"send": "Send Message",
|
||||
"success": "Thank you for your message!",
|
||||
"error": "Please correct the errors below"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"general": "An error occurred",
|
||||
"required_field": "This field is required",
|
||||
"invalid_email": "Please enter a valid email"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Adding New Languages
|
||||
|
||||
### Step 1: Create Translation Files
|
||||
|
||||
**locales/fr.json** (French content):
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"search": "Rechercher",
|
||||
"cart": "Panier",
|
||||
"menu": "Menu",
|
||||
"close": "Fermer"
|
||||
},
|
||||
"product": {
|
||||
"add_to_cart": "Ajouter au panier",
|
||||
"sold_out": "Épuisé"
|
||||
},
|
||||
"cart": {
|
||||
"title": "Votre panier",
|
||||
"empty": "Votre panier est vide",
|
||||
"checkout": "Commander"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**locales/fr.schema.json** (French schema):
|
||||
```json
|
||||
{
|
||||
"sections": {
|
||||
"featured_products": {
|
||||
"name": "Produits vedettes",
|
||||
"settings": {
|
||||
"heading": {
|
||||
"label": "Titre"
|
||||
},
|
||||
"collection": {
|
||||
"label": "Collection"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Use in Liquid
|
||||
|
||||
The `| t` filter automatically uses the correct translation based on the store's language:
|
||||
|
||||
```liquid
|
||||
{%- # Automatically shows "Search" in English, "Rechercher" in French -%}
|
||||
<button>{{ 'general.search' | t }}</button>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Clear, Descriptive Keys
|
||||
```json
|
||||
{%- # Good -%}
|
||||
{
|
||||
"product": {
|
||||
"add_to_cart": "Add to Cart"
|
||||
}
|
||||
}
|
||||
|
||||
{%- # Bad -%}
|
||||
{
|
||||
"prod": {
|
||||
"btn1": "Add to Cart"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Organize Logically
|
||||
```json
|
||||
{
|
||||
"general": { ... }, # General UI
|
||||
"navigation": { ... }, # Navigation items
|
||||
"product": { ... }, # Product-related
|
||||
"cart": { ... }, # Cart-related
|
||||
"forms": { ... } # Forms
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Keep Keys Consistent Across Languages
|
||||
|
||||
**en.default.json**:
|
||||
```json
|
||||
{
|
||||
"product": {
|
||||
"add_to_cart": "Add to Cart"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**fr.json** (same structure):
|
||||
```json
|
||||
{
|
||||
"product": {
|
||||
"add_to_cart": "Ajouter au panier"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Use Nested Objects for Clarity
|
||||
```json
|
||||
{%- # Good -%}
|
||||
{
|
||||
"forms": {
|
||||
"contact": {
|
||||
"name": "Name",
|
||||
"email": "Email"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{%- # Less clear -%}
|
||||
{
|
||||
"forms": {
|
||||
"contact_name": "Name",
|
||||
"contact_email": "Email"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Handle Pluralization
|
||||
|
||||
```json
|
||||
{
|
||||
"cart": {
|
||||
"item_count_one": "{{ count }} item",
|
||||
"item_count_other": "{{ count }} items"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```liquid
|
||||
{% if cart.item_count == 1 %}
|
||||
{{ 'cart.item_count_one' | t: count: cart.item_count }}
|
||||
{% else %}
|
||||
{{ 'cart.item_count_other' | t: count: cart.item_count }}
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### 6. Document Variables
|
||||
|
||||
```json
|
||||
{
|
||||
"_comment": "{{ price }} will be replaced with the actual price",
|
||||
"product": {
|
||||
"from_price": "From {{ price }}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Avoid Hardcoding Text
|
||||
|
||||
```liquid
|
||||
{%- # Bad -%}
|
||||
<h2>Featured Products</h2>
|
||||
|
||||
{%- # Good -%}
|
||||
<h2>{{ 'sections.featured_products.heading' | t }}</h2>
|
||||
```
|
||||
|
||||
## Common Translation Keys
|
||||
|
||||
### Navigation
|
||||
```json
|
||||
{
|
||||
"navigation": {
|
||||
"home": "Home",
|
||||
"shop": "Shop",
|
||||
"collections": "Collections",
|
||||
"about": "About",
|
||||
"contact": "Contact"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Accessibility
|
||||
```json
|
||||
{
|
||||
"accessibility": {
|
||||
"skip_to_content": "Skip to content",
|
||||
"close_menu": "Close menu",
|
||||
"open_menu": "Open menu",
|
||||
"next_slide": "Next slide",
|
||||
"previous_slide": "Previous slide"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Date & Time
|
||||
```json
|
||||
{
|
||||
"date": {
|
||||
"months": {
|
||||
"january": "January",
|
||||
"february": "February"
|
||||
},
|
||||
"days": {
|
||||
"monday": "Monday",
|
||||
"tuesday": "Tuesday"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
**locales/en.default.json**:
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"search": "Search",
|
||||
"cart": "Cart",
|
||||
"menu": "Menu"
|
||||
},
|
||||
"product": {
|
||||
"add_to_cart": "Add to Cart",
|
||||
"from_price": "From {{ price }}"
|
||||
},
|
||||
"cart": {
|
||||
"title": "Your Cart",
|
||||
"empty": "Your cart is empty"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**sections/featured-products.liquid**:
|
||||
```liquid
|
||||
<section>
|
||||
<h2>{{ 'sections.featured_products.heading' | t }}</h2>
|
||||
|
||||
{%- for product in collection.products -%}
|
||||
<div>
|
||||
<h3>{{ product.title }}</h3>
|
||||
<p>{{ 'product.from_price' | t: price: product.price | money }}</p>
|
||||
<button>{{ 'product.add_to_cart' | t }}</button>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</section>
|
||||
```
|
||||
|
||||
Use translation keys consistently to create themes that work seamlessly in multiple languages.
|
||||
367
skills/shopify-liquid-fundamentals/SKILL.md
Normal file
367
skills/shopify-liquid-fundamentals/SKILL.md
Normal file
@@ -0,0 +1,367 @@
|
||||
---
|
||||
name: Shopify Liquid Fundamentals
|
||||
description: Core Shopify Liquid templating best practices for performance, maintainability, and clean code
|
||||
---
|
||||
|
||||
# Shopify Liquid Fundamentals
|
||||
|
||||
## Core Principles
|
||||
|
||||
### Whitespace Control
|
||||
- Use `{%- -%}` to trim whitespace around Liquid tags
|
||||
- Prefer `{% render %}` over deprecated `{% include %}`
|
||||
- Use `{% liquid %}` for multi-line logic blocks
|
||||
|
||||
**Example:**
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign product_available = product.available | default: false
|
||||
assign product_price = product.price | default: 0
|
||||
|
||||
if product == blank
|
||||
assign error_message = 'Product not found'
|
||||
endif
|
||||
-%}
|
||||
```
|
||||
|
||||
### Performance Optimization
|
||||
- Minimize Liquid logic in templates
|
||||
- Use `assign` for complex calculations instead of inline logic
|
||||
- Cache expensive operations
|
||||
- Use snippets for reusable code
|
||||
|
||||
**Good:**
|
||||
```liquid
|
||||
{%- assign discounted_price = product.price | times: 0.9 | money -%}
|
||||
<p>Sale: {{ discounted_price }}</p>
|
||||
```
|
||||
|
||||
**Bad:**
|
||||
```liquid
|
||||
<p>Sale: {{ product.price | times: 0.9 | money }}</p>
|
||||
<p>Save: {{ product.price | times: 0.1 | money }}</p>
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
Always provide defaults and handle empty states:
|
||||
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign heading = section.settings.heading | default: 'Default Heading'
|
||||
assign show_vendor = section.settings.show_vendor | default: false
|
||||
|
||||
if product == blank
|
||||
assign error_message = 'Product unavailable'
|
||||
endif
|
||||
-%}
|
||||
|
||||
{%- if error_message -%}
|
||||
<p class="error">{{ error_message }}</p>
|
||||
{%- else -%}
|
||||
<!-- Normal content -->
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
## Liquid Syntax Essentials
|
||||
|
||||
### Output
|
||||
```liquid
|
||||
{{ variable }} # Output variable
|
||||
{{ variable | escape }} # Escape HTML
|
||||
{{ variable | default: 'fallback' }} # Provide default
|
||||
{{- variable -}} # Trim whitespace
|
||||
```
|
||||
|
||||
### Logic
|
||||
```liquid
|
||||
{% if condition %}
|
||||
{% elsif other_condition %}
|
||||
{% else %}
|
||||
{% endif %}
|
||||
|
||||
{% unless condition %}
|
||||
{% endunless %}
|
||||
|
||||
{% case variable %}
|
||||
{% when 'value1' %}
|
||||
{% when 'value2' %}
|
||||
{% else %}
|
||||
{% endcase %}
|
||||
```
|
||||
|
||||
### Loops
|
||||
```liquid
|
||||
{% for item in collection %}
|
||||
{{ item.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% for item in collection limit: 4 %}
|
||||
{{ item.title }}
|
||||
{% endfor %}
|
||||
|
||||
{% for i in (1..5) %}
|
||||
Item {{ i }}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Variables
|
||||
```liquid
|
||||
{% assign name = 'value' %}
|
||||
{% capture variable %}
|
||||
Content here
|
||||
{% endcapture %}
|
||||
```
|
||||
|
||||
## Common Filters
|
||||
|
||||
### String Filters
|
||||
```liquid
|
||||
{{ 'hello' | capitalize }} # Hello
|
||||
{{ 'HELLO' | downcase }} # hello
|
||||
{{ 'hello' | upcase }} # HELLO
|
||||
{{ 'hello world' | truncate: 8 }} # hello...
|
||||
{{ '<p>test</p>' | escape }} # <p>test</p>
|
||||
{{ 'hello world' | remove: 'world' }} # hello
|
||||
{{ 'hello' | append: ' world' }} # hello world
|
||||
```
|
||||
|
||||
### Array Filters
|
||||
```liquid
|
||||
{{ collection | size }} # Number of items
|
||||
{{ collection | first }} # First item
|
||||
{{ collection | last }} # Last item
|
||||
{{ collection | join: ', ' }} # Join with comma
|
||||
{{ collection | reverse }} # Reverse order
|
||||
{{ collection | sort: 'title' }} # Sort by property
|
||||
```
|
||||
|
||||
### Math Filters
|
||||
```liquid
|
||||
{{ 10 | plus: 5 }} # 15
|
||||
{{ 10 | minus: 5 }} # 5
|
||||
{{ 10 | times: 5 }} # 50
|
||||
{{ 10 | divided_by: 5 }} # 2
|
||||
{{ 10.5 | ceil }} # 11
|
||||
{{ 10.5 | floor }} # 10
|
||||
{{ 10.5 | round }} # 11
|
||||
```
|
||||
|
||||
### Money Filters
|
||||
```liquid
|
||||
{{ 1000 | money }} # $10.00
|
||||
{{ 1000 | money_with_currency }} # $10.00 USD
|
||||
{{ 1000 | money_without_currency }} # 10.00
|
||||
```
|
||||
|
||||
### Image Filters
|
||||
```liquid
|
||||
{{ image | image_url: width: 400 }}
|
||||
{{ image | image_url: width: 400, height: 400 }}
|
||||
{{ image | image_tag }}
|
||||
{{ image | image_tag: alt: 'Description' }}
|
||||
```
|
||||
|
||||
## Shopify Objects
|
||||
|
||||
### Global Objects
|
||||
```liquid
|
||||
{{ shop.name }} # Store name
|
||||
{{ shop.email }} # Store email
|
||||
{{ cart.item_count }} # Cart items
|
||||
{{ customer.name }} # Customer name (if logged in)
|
||||
{{ settings.color_primary }} # Theme setting
|
||||
```
|
||||
|
||||
### Product Objects
|
||||
```liquid
|
||||
{{ product.title }}
|
||||
{{ product.price }}
|
||||
{{ product.compare_at_price }}
|
||||
{{ product.available }}
|
||||
{{ product.vendor }}
|
||||
{{ product.type }}
|
||||
{{ product.featured_image }}
|
||||
{{ product.url }}
|
||||
```
|
||||
|
||||
### Collection Objects
|
||||
```liquid
|
||||
{{ collection.title }}
|
||||
{{ collection.description }}
|
||||
{{ collection.products }}
|
||||
{{ collection.products_count }}
|
||||
{{ collection.url }}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Escape User Input
|
||||
```liquid
|
||||
<h1>{{ product.title | escape }}</h1>
|
||||
<p>{{ customer.name | escape }}</p>
|
||||
```
|
||||
|
||||
### 2. Provide Defaults
|
||||
```liquid
|
||||
{%- assign heading = section.settings.heading | default: 'Default Title' -%}
|
||||
{%- assign image = product.featured_image | default: blank -%}
|
||||
```
|
||||
|
||||
### 3. Check for Blank Values
|
||||
```liquid
|
||||
{%- if heading != blank -%}
|
||||
<h2>{{ heading | escape }}</h2>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 4. Use Liquid Tag for Multi-line Logic
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign is_sale = false
|
||||
if product.compare_at_price > product.price
|
||||
assign is_sale = true
|
||||
endif
|
||||
|
||||
assign discount_percent = product.compare_at_price | minus: product.price | times: 100 | divided_by: product.compare_at_price
|
||||
-%}
|
||||
```
|
||||
|
||||
### 5. Minimize Nested Conditions
|
||||
```liquid
|
||||
{%- # Bad -%}
|
||||
{% if product.available %}
|
||||
{% if product.compare_at_price > product.price %}
|
||||
<span>On Sale</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{%- # Good -%}
|
||||
{%- liquid
|
||||
assign is_on_sale = false
|
||||
if product.available and product.compare_at_price > product.price
|
||||
assign is_on_sale = true
|
||||
endif
|
||||
-%}
|
||||
|
||||
{%- if is_on_sale -%}
|
||||
<span>On Sale</span>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Conditional Class Names
|
||||
```liquid
|
||||
<div class="product{% if product.available %} in-stock{% else %} out-of-stock{% endif %}">
|
||||
<!-- Content -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### Loop with Index
|
||||
```liquid
|
||||
{%- for product in collection.products -%}
|
||||
<div class="product-{{ forloop.index }}">
|
||||
{%- if forloop.first -%}
|
||||
<span>Featured</span>
|
||||
{%- endif -%}
|
||||
{{ product.title }}
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
```
|
||||
|
||||
### Empty State Handling
|
||||
```liquid
|
||||
{%- if collection.products.size > 0 -%}
|
||||
{%- for product in collection.products -%}
|
||||
<!-- Product markup -->
|
||||
{%- endfor -%}
|
||||
{%- else -%}
|
||||
<p>No products available.</p>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### Responsive Images
|
||||
```liquid
|
||||
{%- if product.featured_image -%}
|
||||
<img
|
||||
srcset="
|
||||
{{ product.featured_image | image_url: width: 400 }} 400w,
|
||||
{{ product.featured_image | image_url: width: 800 }} 800w,
|
||||
{{ product.featured_image | image_url: width: 1200 }} 1200w
|
||||
"
|
||||
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||
src="{{ product.featured_image | image_url: width: 800 }}"
|
||||
alt="{{ product.featured_image.alt | escape }}"
|
||||
loading="lazy"
|
||||
width="800"
|
||||
height="{{ 800 | divided_by: product.featured_image.aspect_ratio | ceil }}"
|
||||
>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
## Rendering Snippets
|
||||
|
||||
### Basic Rendering
|
||||
```liquid
|
||||
{% render 'product-card' %}
|
||||
```
|
||||
|
||||
### With Parameters
|
||||
```liquid
|
||||
{% render 'product-card', product: product, show_vendor: true %}
|
||||
```
|
||||
|
||||
### With Multiple Parameters
|
||||
```liquid
|
||||
{% render 'button',
|
||||
text: 'Add to Cart',
|
||||
url: product.url,
|
||||
style: 'primary',
|
||||
size: 'large'
|
||||
%}
|
||||
```
|
||||
|
||||
## Performance Tips
|
||||
|
||||
1. **Limit loops** - Use `limit` parameter when possible
|
||||
2. **Cache expensive operations** - Assign to variables
|
||||
3. **Minimize API calls** - Don't call same object property multiple times
|
||||
4. **Use snippets wisely** - Balance between reusability and overhead
|
||||
5. **Optimize images** - Use appropriate sizes with image filters
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
❌ **Don't repeat expensive operations:**
|
||||
```liquid
|
||||
<p>{{ product.price | times: 1.1 | money }}</p>
|
||||
<p>{{ product.price | times: 1.1 | money }}</p>
|
||||
```
|
||||
|
||||
✅ **Do assign to variable:**
|
||||
```liquid
|
||||
{%- assign price_with_tax = product.price | times: 1.1 | money -%}
|
||||
<p>{{ price_with_tax }}</p>
|
||||
<p>{{ price_with_tax }}</p>
|
||||
```
|
||||
|
||||
❌ **Don't forget to escape:**
|
||||
```liquid
|
||||
<h1>{{ product.title }}</h1>
|
||||
```
|
||||
|
||||
✅ **Do escape user input:**
|
||||
```liquid
|
||||
<h1>{{ product.title | escape }}</h1>
|
||||
```
|
||||
|
||||
❌ **Don't use deprecated include:**
|
||||
```liquid
|
||||
{% include 'snippet-name' %}
|
||||
```
|
||||
|
||||
✅ **Do use render:**
|
||||
```liquid
|
||||
{% render 'snippet-name' %}
|
||||
```
|
||||
|
||||
Follow these fundamentals for clean, performant, maintainable Shopify Liquid code.
|
||||
674
skills/shopify-schema-design/SKILL.md
Normal file
674
skills/shopify-schema-design/SKILL.md
Normal file
@@ -0,0 +1,674 @@
|
||||
---
|
||||
name: Shopify Schema Design
|
||||
description: Best practices for creating comprehensive section schemas with all setting types
|
||||
---
|
||||
|
||||
# Shopify Schema Design
|
||||
|
||||
## Schema Structure
|
||||
|
||||
The `{% schema %}` tag defines how a section appears in the theme editor:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Section Name",
|
||||
"tag": "section",
|
||||
"class": "section-class",
|
||||
"limit": 1,
|
||||
"settings": [...],
|
||||
"blocks": [...],
|
||||
"presets": [...]
|
||||
}
|
||||
```
|
||||
|
||||
## Setting Types
|
||||
|
||||
### Text Input
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"id": "heading",
|
||||
"label": "Heading",
|
||||
"default": "Default text",
|
||||
"info": "Helpful description",
|
||||
"placeholder": "Enter text here"
|
||||
}
|
||||
```
|
||||
|
||||
### Textarea
|
||||
```json
|
||||
{
|
||||
"type": "textarea",
|
||||
"id": "description",
|
||||
"label": "Description",
|
||||
"default": "Default description"
|
||||
}
|
||||
```
|
||||
|
||||
### Rich Text
|
||||
```json
|
||||
{
|
||||
"type": "richtext",
|
||||
"id": "content",
|
||||
"label": "Content",
|
||||
"default": "<p>Default rich text content</p>"
|
||||
}
|
||||
```
|
||||
|
||||
### Number / Range
|
||||
```json
|
||||
{
|
||||
"type": "range",
|
||||
"id": "padding",
|
||||
"label": "Padding",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 4,
|
||||
"unit": "px",
|
||||
"default": 40,
|
||||
"info": "Section padding in pixels"
|
||||
}
|
||||
```
|
||||
|
||||
### Checkbox
|
||||
```json
|
||||
{
|
||||
"type": "checkbox",
|
||||
"id": "show_vendor",
|
||||
"label": "Show Product Vendor",
|
||||
"default": true,
|
||||
"info": "Display vendor name on product cards"
|
||||
}
|
||||
```
|
||||
|
||||
### Select / Dropdown
|
||||
```json
|
||||
{
|
||||
"type": "select",
|
||||
"id": "layout",
|
||||
"label": "Layout Style",
|
||||
"options": [
|
||||
{
|
||||
"value": "grid",
|
||||
"label": "Grid"
|
||||
},
|
||||
{
|
||||
"value": "carousel",
|
||||
"label": "Carousel"
|
||||
},
|
||||
{
|
||||
"value": "list",
|
||||
"label": "List"
|
||||
}
|
||||
],
|
||||
"default": "grid",
|
||||
"info": "Choose how products are displayed"
|
||||
}
|
||||
```
|
||||
|
||||
### Radio Buttons
|
||||
```json
|
||||
{
|
||||
"type": "radio",
|
||||
"id": "alignment",
|
||||
"label": "Text Alignment",
|
||||
"options": [
|
||||
{
|
||||
"value": "left",
|
||||
"label": "Left"
|
||||
},
|
||||
{
|
||||
"value": "center",
|
||||
"label": "Center"
|
||||
},
|
||||
{
|
||||
"value": "right",
|
||||
"label": "Right"
|
||||
}
|
||||
],
|
||||
"default": "center"
|
||||
}
|
||||
```
|
||||
|
||||
### Color Picker
|
||||
```json
|
||||
{
|
||||
"type": "color",
|
||||
"id": "bg_color",
|
||||
"label": "Background Color",
|
||||
"default": "#ffffff",
|
||||
"info": "Section background color"
|
||||
}
|
||||
```
|
||||
|
||||
### Color Background
|
||||
```json
|
||||
{
|
||||
"type": "color_background",
|
||||
"id": "background_gradient",
|
||||
"label": "Background Gradient",
|
||||
"info": "Supports solid colors and gradients"
|
||||
}
|
||||
```
|
||||
|
||||
### Image Picker
|
||||
```json
|
||||
{
|
||||
"type": "image_picker",
|
||||
"id": "image",
|
||||
"label": "Image",
|
||||
"info": "Recommended size: 1600 x 900 px"
|
||||
}
|
||||
```
|
||||
|
||||
### Font Picker
|
||||
```json
|
||||
{
|
||||
"type": "font_picker",
|
||||
"id": "heading_font",
|
||||
"label": "Heading Font",
|
||||
"default": "assistant_n4",
|
||||
"info": "Choose font for headings"
|
||||
}
|
||||
```
|
||||
|
||||
### Collection Picker
|
||||
```json
|
||||
{
|
||||
"type": "collection",
|
||||
"id": "collection",
|
||||
"label": "Collection"
|
||||
}
|
||||
```
|
||||
|
||||
### Product Picker
|
||||
```json
|
||||
{
|
||||
"type": "product",
|
||||
"id": "featured_product",
|
||||
"label": "Featured Product"
|
||||
}
|
||||
```
|
||||
|
||||
### Product List
|
||||
```json
|
||||
{
|
||||
"type": "product_list",
|
||||
"id": "products",
|
||||
"label": "Products",
|
||||
"limit": 12,
|
||||
"info": "Select up to 12 products"
|
||||
}
|
||||
```
|
||||
|
||||
### Collection List
|
||||
```json
|
||||
{
|
||||
"type": "collection_list",
|
||||
"id": "collections",
|
||||
"label": "Collections",
|
||||
"limit": 8
|
||||
}
|
||||
```
|
||||
|
||||
### Blog Picker
|
||||
```json
|
||||
{
|
||||
"type": "blog",
|
||||
"id": "blog",
|
||||
"label": "Blog"
|
||||
}
|
||||
```
|
||||
|
||||
### Article Picker
|
||||
```json
|
||||
{
|
||||
"type": "article",
|
||||
"id": "article",
|
||||
"label": "Article"
|
||||
}
|
||||
```
|
||||
|
||||
### Page Picker
|
||||
```json
|
||||
{
|
||||
"type": "page",
|
||||
"id": "page",
|
||||
"label": "Page"
|
||||
}
|
||||
```
|
||||
|
||||
### Link / URL
|
||||
```json
|
||||
{
|
||||
"type": "url",
|
||||
"id": "button_link",
|
||||
"label": "Button Link",
|
||||
"info": "Link to internal page or external URL"
|
||||
}
|
||||
```
|
||||
|
||||
### Video URL
|
||||
```json
|
||||
{
|
||||
"type": "video_url",
|
||||
"id": "video",
|
||||
"label": "Video URL",
|
||||
"accept": ["youtube", "vimeo"],
|
||||
"info": "Supports YouTube and Vimeo"
|
||||
}
|
||||
```
|
||||
|
||||
### HTML
|
||||
```json
|
||||
{
|
||||
"type": "html",
|
||||
"id": "custom_html",
|
||||
"label": "Custom HTML",
|
||||
"info": "Add custom HTML code"
|
||||
}
|
||||
```
|
||||
|
||||
### Liquid
|
||||
```json
|
||||
{
|
||||
"type": "liquid",
|
||||
"id": "custom_liquid",
|
||||
"label": "Custom Liquid",
|
||||
"info": "Add custom Liquid code"
|
||||
}
|
||||
```
|
||||
|
||||
## Organizational Elements
|
||||
|
||||
### Header
|
||||
```json
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Section Heading"
|
||||
}
|
||||
```
|
||||
|
||||
### Paragraph
|
||||
```json
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": "Helpful information or instructions for merchants."
|
||||
}
|
||||
```
|
||||
|
||||
## Complete Schema Example
|
||||
|
||||
```json
|
||||
{% schema %}
|
||||
{
|
||||
"name": "Product Grid",
|
||||
"tag": "section",
|
||||
"class": "product-grid-section",
|
||||
"settings": [
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Content"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "heading",
|
||||
"label": "Heading",
|
||||
"default": "Featured Products"
|
||||
},
|
||||
{
|
||||
"type": "richtext",
|
||||
"id": "description",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"type": "collection",
|
||||
"id": "collection",
|
||||
"label": "Collection"
|
||||
},
|
||||
{
|
||||
"type": "range",
|
||||
"id": "products_count",
|
||||
"label": "Number of Products",
|
||||
"min": 2,
|
||||
"max": 12,
|
||||
"step": 1,
|
||||
"default": 4
|
||||
},
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Layout"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "layout",
|
||||
"label": "Layout Style",
|
||||
"options": [
|
||||
{
|
||||
"value": "grid",
|
||||
"label": "Grid"
|
||||
},
|
||||
{
|
||||
"value": "carousel",
|
||||
"label": "Carousel"
|
||||
}
|
||||
],
|
||||
"default": "grid"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "columns_desktop",
|
||||
"label": "Desktop Columns",
|
||||
"options": [
|
||||
{
|
||||
"value": "2",
|
||||
"label": "2"
|
||||
},
|
||||
{
|
||||
"value": "3",
|
||||
"label": "3"
|
||||
},
|
||||
{
|
||||
"value": "4",
|
||||
"label": "4"
|
||||
}
|
||||
],
|
||||
"default": "4"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "columns_mobile",
|
||||
"label": "Mobile Columns",
|
||||
"options": [
|
||||
{
|
||||
"value": "1",
|
||||
"label": "1"
|
||||
},
|
||||
{
|
||||
"value": "2",
|
||||
"label": "2"
|
||||
}
|
||||
],
|
||||
"default": "1"
|
||||
},
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Product Card"
|
||||
},
|
||||
{
|
||||
"type": "checkbox",
|
||||
"id": "show_vendor",
|
||||
"label": "Show Product Vendor",
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"type": "checkbox",
|
||||
"id": "show_quick_view",
|
||||
"label": "Enable Quick View",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Colors"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"id": "bg_color",
|
||||
"label": "Background Color",
|
||||
"default": "#ffffff"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"id": "text_color",
|
||||
"label": "Text Color",
|
||||
"default": "#000000"
|
||||
},
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Spacing"
|
||||
},
|
||||
{
|
||||
"type": "range",
|
||||
"id": "padding_top",
|
||||
"label": "Top Spacing",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 4,
|
||||
"unit": "px",
|
||||
"default": 40
|
||||
},
|
||||
{
|
||||
"type": "range",
|
||||
"id": "padding_bottom",
|
||||
"label": "Bottom Spacing",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 4,
|
||||
"unit": "px",
|
||||
"default": 40
|
||||
}
|
||||
],
|
||||
"blocks": [
|
||||
{
|
||||
"type": "featured_product",
|
||||
"name": "Featured Product",
|
||||
"settings": [
|
||||
{
|
||||
"type": "product",
|
||||
"id": "product",
|
||||
"label": "Product"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"presets": [
|
||||
{
|
||||
"name": "Product Grid",
|
||||
"settings": {
|
||||
"heading": "Featured Products",
|
||||
"products_count": 4
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endschema %}
|
||||
```
|
||||
|
||||
## Blocks Configuration
|
||||
|
||||
### Basic Blocks
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "heading",
|
||||
"name": "Heading",
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "text",
|
||||
"label": "Text",
|
||||
"default": "Heading"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "paragraph",
|
||||
"name": "Paragraph",
|
||||
"settings": [
|
||||
{
|
||||
"type": "richtext",
|
||||
"id": "content",
|
||||
"label": "Content"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"max_blocks": 10
|
||||
}
|
||||
```
|
||||
|
||||
### Accept All Theme Blocks
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "@theme"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Accept App Blocks
|
||||
```json
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"type": "@app"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Presets
|
||||
|
||||
### Basic Preset
|
||||
```json
|
||||
{
|
||||
"presets": [
|
||||
{
|
||||
"name": "Hero Banner"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Preset with Default Settings
|
||||
```json
|
||||
{
|
||||
"presets": [
|
||||
{
|
||||
"name": "Product Grid",
|
||||
"settings": {
|
||||
"heading": "Featured Products",
|
||||
"products_count": 4,
|
||||
"columns_desktop": "4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Preset with Blocks
|
||||
```json
|
||||
{
|
||||
"presets": [
|
||||
{
|
||||
"name": "Testimonials",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "testimonial"
|
||||
},
|
||||
{
|
||||
"type": "testimonial"
|
||||
},
|
||||
{
|
||||
"type": "testimonial"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Group Related Settings with Headers
|
||||
```json
|
||||
{
|
||||
"settings": [
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Content Settings"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "heading",
|
||||
"label": "Heading"
|
||||
},
|
||||
{
|
||||
"type": "header",
|
||||
"content": "Layout Settings"
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"id": "columns",
|
||||
"label": "Columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Always Provide Defaults
|
||||
```json
|
||||
{
|
||||
"type": "text",
|
||||
"id": "heading",
|
||||
"label": "Heading",
|
||||
"default": "Default Heading" ← Always provide!
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Add Info Text for Complex Settings
|
||||
```json
|
||||
{
|
||||
"type": "range",
|
||||
"id": "products_count",
|
||||
"label": "Number of Products",
|
||||
"min": 2,
|
||||
"max": 12,
|
||||
"default": 4,
|
||||
"info": "Maximum number of products to display from the selected collection"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Use Appropriate Input Types
|
||||
- Text/numbers → `text`, `textarea`, `number`, `range`
|
||||
- True/false → `checkbox`
|
||||
- Multiple options → `select`, `radio`
|
||||
- Resources → `collection`, `product`, `image_picker`
|
||||
- Colors → `color`, `color_background`
|
||||
|
||||
### 5. Set Reasonable Limits
|
||||
```json
|
||||
{
|
||||
"type": "range",
|
||||
"id": "padding",
|
||||
"min": 0, ← Minimum value
|
||||
"max": 100, ← Maximum value
|
||||
"step": 4, ← Increment
|
||||
"default": 40 ← Sensible default
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Include Presets for Quick Setup
|
||||
```json
|
||||
{
|
||||
"presets": [
|
||||
{
|
||||
"name": "Section Name",
|
||||
"settings": {
|
||||
"heading": "Default Heading",
|
||||
"show_feature": true
|
||||
},
|
||||
"blocks": [
|
||||
{
|
||||
"type": "block_type"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Create schemas that make section customization intuitive and efficient for merchants.
|
||||
518
skills/shopify-section-patterns/SKILL.md
Normal file
518
skills/shopify-section-patterns/SKILL.md
Normal file
@@ -0,0 +1,518 @@
|
||||
---
|
||||
name: Shopify Section Patterns
|
||||
description: Best practices for creating Shopify 2.0 sections with inline CSS and JavaScript
|
||||
---
|
||||
|
||||
# Shopify Section Patterns
|
||||
|
||||
## Section Anatomy
|
||||
|
||||
A complete Shopify section consists of:
|
||||
1. **Liquid markup** - HTML structure with dynamic content
|
||||
2. **Inline CSS** - `{% stylesheet %}` tag (optional)
|
||||
3. **Inline JavaScript** - `{% javascript %}` tag (optional)
|
||||
4. **Schema** - `{% schema %}` JSON configuration
|
||||
|
||||
## Complete Section Template
|
||||
|
||||
```liquid
|
||||
{%- comment -%}
|
||||
Section: [Name]
|
||||
Description: [Brief description]
|
||||
Usage: Add via Theme Editor
|
||||
{%- endcomment -%}
|
||||
|
||||
{%- liquid
|
||||
assign heading = section.settings.heading | default: 'Default Heading'
|
||||
assign bg_color = section.settings.bg_color
|
||||
assign text_color = section.settings.text_color
|
||||
-%}
|
||||
|
||||
<section
|
||||
id="section-{{ section.id }}"
|
||||
class="my-section"
|
||||
data-section-id="{{ section.id }}"
|
||||
>
|
||||
<div class="container">
|
||||
{%- if heading != blank -%}
|
||||
<h2>{{ heading | escape }}</h2>
|
||||
{%- endif -%}
|
||||
|
||||
{%- comment -%} Section content {%- endcomment -%}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{% stylesheet %}
|
||||
#section-{{ section.id }} {
|
||||
padding: {{ section.settings.padding_top }}px 0 {{ section.settings.padding_bottom }}px;
|
||||
background-color: {{ bg_color }};
|
||||
color: {{ text_color }};
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
/* Tablet styles */
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
/* Desktop styles */
|
||||
}
|
||||
{% endstylesheet %}
|
||||
|
||||
{% javascript %}
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function initSection(sectionId) {
|
||||
const section = document.querySelector('[data-section-id="' + sectionId + '"]');
|
||||
if (!section) return;
|
||||
|
||||
// Your JavaScript here
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initSection('{{ section.id }}');
|
||||
});
|
||||
|
||||
// Re-initialize when section reloads in theme editor
|
||||
document.addEventListener('shopify:section:load', function(event) {
|
||||
if (event.detail.sectionId === '{{ section.id }}') {
|
||||
initSection('{{ section.id }}');
|
||||
}
|
||||
});
|
||||
})();
|
||||
{% endjavascript %}
|
||||
|
||||
{% schema %}
|
||||
{
|
||||
"name": "Section Name",
|
||||
"tag": "section",
|
||||
"class": "section-wrapper",
|
||||
"settings": [
|
||||
{
|
||||
"type": "text",
|
||||
"id": "heading",
|
||||
"label": "Heading",
|
||||
"default": "Default Heading"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"id": "bg_color",
|
||||
"label": "Background Color",
|
||||
"default": "#ffffff"
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"id": "text_color",
|
||||
"label": "Text Color",
|
||||
"default": "#000000"
|
||||
},
|
||||
{
|
||||
"type": "range",
|
||||
"id": "padding_top",
|
||||
"label": "Top Spacing",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 4,
|
||||
"unit": "px",
|
||||
"default": 40
|
||||
},
|
||||
{
|
||||
"type": "range",
|
||||
"id": "padding_bottom",
|
||||
"label": "Bottom Spacing",
|
||||
"min": 0,
|
||||
"max": 100,
|
||||
"step": 4,
|
||||
"unit": "px",
|
||||
"default": 40
|
||||
}
|
||||
],
|
||||
"presets": [
|
||||
{
|
||||
"name": "Section Name"
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endschema %}
|
||||
```
|
||||
|
||||
## When to Use Inline CSS/JS
|
||||
|
||||
### Use Inline `{% stylesheet %}` When:
|
||||
- CSS needs Liquid variables: `{{ section.settings.color }}`
|
||||
- Dynamic styles based on merchant settings
|
||||
- Section-specific styles that don't need external file
|
||||
|
||||
### Use Inline `{% javascript %}` When:
|
||||
- JS needs Liquid settings: `{{ section.settings.enable_feature }}`
|
||||
- Section-specific functionality
|
||||
- Need access to `{{ section.id }}` or other Liquid values
|
||||
|
||||
### Skip Inline Tags When:
|
||||
- Styles/scripts are static and reusable
|
||||
- Better to use theme.css or theme.js
|
||||
- No Liquid variables needed
|
||||
|
||||
## Common Section Patterns
|
||||
|
||||
### 1. Product Grid Section
|
||||
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign collection = section.settings.collection
|
||||
assign products_count = section.settings.products_count
|
||||
assign columns = section.settings.columns_desktop
|
||||
-%}
|
||||
|
||||
<div class="product-grid">
|
||||
{%- for product in collection.products limit: products_count -%}
|
||||
<div class="product-item">
|
||||
<a href="{{ product.url }}">
|
||||
{%- if product.featured_image -%}
|
||||
<img
|
||||
src="{{ product.featured_image | image_url: width: 400 }}"
|
||||
alt="{{ product.featured_image.alt | escape }}"
|
||||
loading="lazy"
|
||||
>
|
||||
{%- endif -%}
|
||||
|
||||
<h3>{{ product.title | escape }}</h3>
|
||||
<p>{{ product.price | money }}</p>
|
||||
</a>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
{% stylesheet %}
|
||||
.product-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat({{ columns }}, 1fr);
|
||||
}
|
||||
}
|
||||
{% endstylesheet %}
|
||||
```
|
||||
|
||||
### 2. Hero Banner Section
|
||||
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign image = section.settings.image
|
||||
assign heading = section.settings.heading
|
||||
assign text = section.settings.text
|
||||
assign button_label = section.settings.button_label
|
||||
assign button_link = section.settings.button_link
|
||||
-%}
|
||||
|
||||
<div class="hero-banner">
|
||||
{%- if image -%}
|
||||
<div class="hero-banner__image">
|
||||
<img
|
||||
src="{{ image | image_url: width: 1600 }}"
|
||||
alt="{{ image.alt | escape }}"
|
||||
>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
<div class="hero-banner__content">
|
||||
{%- if heading != blank -%}
|
||||
<h1>{{ heading | escape }}</h1>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if text != blank -%}
|
||||
<p>{{ text }}</p>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if button_label != blank and button_link != blank -%}
|
||||
<a href="{{ button_link }}" class="button">
|
||||
{{ button_label | escape }}
|
||||
</a>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% stylesheet %}
|
||||
.hero-banner {
|
||||
position: relative;
|
||||
min-height: 500px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-banner__image {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.hero-banner__image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.hero-banner__content {
|
||||
padding: 2rem;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
padding: 1rem 2rem;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
{% endstylesheet %}
|
||||
```
|
||||
|
||||
### 3. Testimonials Section with Blocks
|
||||
|
||||
```liquid
|
||||
<div class="testimonials">
|
||||
{%- for block in section.blocks -%}
|
||||
<div class="testimonial" {{ block.shopify_attributes }}>
|
||||
{%- if block.settings.quote != blank -%}
|
||||
<blockquote>
|
||||
{{ block.settings.quote }}
|
||||
</blockquote>
|
||||
{%- endif -%}
|
||||
|
||||
{%- if block.settings.author != blank -%}
|
||||
<cite>{{ block.settings.author | escape }}</cite>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
{% schema %}
|
||||
{
|
||||
"name": "Testimonials",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "testimonial",
|
||||
"name": "Testimonial",
|
||||
"settings": [
|
||||
{
|
||||
"type": "textarea",
|
||||
"id": "quote",
|
||||
"label": "Quote"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"id": "author",
|
||||
"label": "Author"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"presets": [
|
||||
{
|
||||
"name": "Testimonials",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "testimonial"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
{% endschema %}
|
||||
```
|
||||
|
||||
## Shopify Section Events
|
||||
|
||||
Handle theme editor events:
|
||||
|
||||
```javascript
|
||||
{% javascript %}
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
function initSection(sectionId) {
|
||||
const section = document.querySelector('[data-section-id="' + sectionId + '"]');
|
||||
if (!section) return;
|
||||
|
||||
console.log('Section initialized:', sectionId);
|
||||
}
|
||||
|
||||
function cleanupSection(sectionId) {
|
||||
// Clean up event listeners, timers, etc.
|
||||
console.log('Section cleaned up:', sectionId);
|
||||
}
|
||||
|
||||
// Load event - section added or page loaded
|
||||
document.addEventListener('shopify:section:load', function(event) {
|
||||
initSection(event.detail.sectionId);
|
||||
});
|
||||
|
||||
// Unload event - section removed
|
||||
document.addEventListener('shopify:section:unload', function(event) {
|
||||
cleanupSection(event.detail.sectionId);
|
||||
});
|
||||
|
||||
// Select event - section selected in theme editor
|
||||
document.addEventListener('shopify:section:select', function(event) {
|
||||
console.log('Section selected:', event.detail.sectionId);
|
||||
});
|
||||
|
||||
// Deselect event - section deselected in theme editor
|
||||
document.addEventListener('shopify:section:deselect', function(event) {
|
||||
console.log('Section deselected:', event.detail.sectionId);
|
||||
});
|
||||
|
||||
// Block select event - block selected in theme editor
|
||||
document.addEventListener('shopify:block:select', function(event) {
|
||||
console.log('Block selected:', event.detail.blockId);
|
||||
});
|
||||
|
||||
// Block deselect event - block deselected in theme editor
|
||||
document.addEventListener('shopify:block:deselect', function(event) {
|
||||
console.log('Block deselected:', event.detail.blockId);
|
||||
});
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initSection('{{ section.id }}');
|
||||
});
|
||||
})();
|
||||
{% endjavascript %}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Section ID for Unique Styling
|
||||
```liquid
|
||||
<div id="section-{{ section.id }}" class="my-section">
|
||||
<!-- Content -->
|
||||
</div>
|
||||
|
||||
{% stylesheet %}
|
||||
#section-{{ section.id }} {
|
||||
/* Section-specific styles using Liquid variables */
|
||||
background: {{ section.settings.bg_color }};
|
||||
}
|
||||
|
||||
.my-section {
|
||||
/* General styles without Liquid variables */
|
||||
padding: 2rem 0;
|
||||
}
|
||||
{% endstylesheet %}
|
||||
```
|
||||
|
||||
### 2. Provide Sensible Defaults
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign heading = section.settings.heading | default: 'Default Heading'
|
||||
assign columns = section.settings.columns | default: 3
|
||||
assign show_prices = section.settings.show_prices | default: true
|
||||
-%}
|
||||
```
|
||||
|
||||
### 3. Handle Empty States
|
||||
```liquid
|
||||
{%- if collection.products.size > 0 -%}
|
||||
<!-- Show products -->
|
||||
{%- else -%}
|
||||
<p>No products available in this collection.</p>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 4. Mobile-First Responsive
|
||||
```css
|
||||
/* Mobile first - base styles */
|
||||
.grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
/* Tablet and up */
|
||||
@media (min-width: 768px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Desktop and up */
|
||||
@media (min-width: 1024px) {
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Use data- Attributes for JavaScript
|
||||
```liquid
|
||||
<div
|
||||
class="slider"
|
||||
data-autoplay="{{ section.settings.autoplay }}"
|
||||
data-speed="{{ section.settings.speed }}"
|
||||
>
|
||||
<!-- Slider content -->
|
||||
</div>
|
||||
|
||||
{% javascript %}
|
||||
const slider = document.querySelector('.slider');
|
||||
const autoplay = slider.dataset.autoplay === 'true';
|
||||
const speed = parseInt(slider.dataset.speed, 10);
|
||||
{% endjavascript %}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Conditional Classes
|
||||
```liquid
|
||||
<div class="card{% if product.available %} in-stock{% else %} sold-out{% endif %}">
|
||||
<!-- Card content -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### Dynamic Grid Columns
|
||||
```liquid
|
||||
{% stylesheet %}
|
||||
.product-grid {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
grid-template-columns: repeat({{ section.settings.columns_mobile }}, 1fr);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.product-grid {
|
||||
grid-template-columns: repeat({{ section.settings.columns_desktop }}, 1fr);
|
||||
}
|
||||
}
|
||||
{% endstylesheet %}
|
||||
```
|
||||
|
||||
### Loading States
|
||||
```javascript
|
||||
{% javascript %}
|
||||
function addToCart(button) {
|
||||
button.classList.add('loading');
|
||||
button.disabled = true;
|
||||
|
||||
// Add to cart logic...
|
||||
|
||||
button.classList.remove('loading');
|
||||
button.disabled = false;
|
||||
}
|
||||
{% endjavascript %}
|
||||
```
|
||||
|
||||
Create sections that are flexible, performant, and easy for merchants to customize through the theme editor.
|
||||
436
skills/shopify-snippet-library/SKILL.md
Normal file
436
skills/shopify-snippet-library/SKILL.md
Normal file
@@ -0,0 +1,436 @@
|
||||
---
|
||||
name: Shopify Snippet Library
|
||||
description: Reusable Liquid snippet patterns with proper documentation and parameter handling
|
||||
---
|
||||
|
||||
# Shopify Snippet Library
|
||||
|
||||
## Snippet Documentation Format
|
||||
|
||||
All snippets should include documentation using Liquid comments:
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: [Name]
|
||||
Description: [What this snippet does]
|
||||
|
||||
Parameters:
|
||||
- param_name {type} - Description [required/optional]
|
||||
|
||||
Usage:
|
||||
{% render 'snippet-name', param_name: value %}
|
||||
|
||||
Example:
|
||||
{% render 'product-card', product: product, show_vendor: true %}
|
||||
{% endcomment %}
|
||||
```
|
||||
|
||||
## Common Snippet Patterns
|
||||
|
||||
### 1. Product Card
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Product Card
|
||||
Description: Displays a product with image, title, vendor, and price
|
||||
|
||||
Parameters:
|
||||
- product {product} - The product object [required]
|
||||
- show_vendor {boolean} - Show product vendor (default: false) [optional]
|
||||
- image_size {number} - Image width in pixels (default: 400) [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'product-card', product: product, show_vendor: true, image_size: 600 %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign show_vendor = show_vendor | default: false
|
||||
assign image_size = image_size | default: 400
|
||||
-%}
|
||||
|
||||
{%- if product == blank -%}
|
||||
<div class="product-card product-card--empty {{ class }}">
|
||||
<p>Product not available</p>
|
||||
</div>
|
||||
{%- else -%}
|
||||
<article class="product-card {{ class }}">
|
||||
<a href="{{ product.url }}" class="product-card__link">
|
||||
{%- if product.featured_image -%}
|
||||
<div class="product-card__image">
|
||||
<img
|
||||
src="{{ product.featured_image | image_url: width: image_size }}"
|
||||
alt="{{ product.featured_image.alt | escape }}"
|
||||
loading="lazy"
|
||||
width="{{ image_size }}"
|
||||
height="{{ image_size | divided_by: product.featured_image.aspect_ratio | ceil }}"
|
||||
>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
<div class="product-card__info">
|
||||
{%- if show_vendor and product.vendor != blank -%}
|
||||
<p class="product-card__vendor">{{ product.vendor | escape }}</p>
|
||||
{%- endif -%}
|
||||
|
||||
<h3 class="product-card__title">{{ product.title | escape }}</h3>
|
||||
|
||||
<div class="product-card__price">
|
||||
{%- if product.compare_at_price > product.price -%}
|
||||
<span class="product-card__price--sale">{{ product.price | money }}</span>
|
||||
<span class="product-card__price--compare">{{ product.compare_at_price | money }}</span>
|
||||
{%- else -%}
|
||||
<span>{{ product.price | money }}</span>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 2. Button / Link
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Button
|
||||
Description: Renders a customizable button or link
|
||||
|
||||
Parameters:
|
||||
- text {string} - Button text [required]
|
||||
- url {string} - Button URL (makes it a link) [optional]
|
||||
- style {string} - Button style: 'primary', 'secondary', 'outline' (default: 'primary') [optional]
|
||||
- size {string} - Button size: 'small', 'medium', 'large' (default: 'medium') [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'button', text: 'Add to Cart', style: 'primary', size: 'large' %}
|
||||
{% render 'button', text: 'Learn More', url: '/pages/about', style: 'outline' %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign style = style | default: 'primary'
|
||||
assign size = size | default: 'medium'
|
||||
assign btn_class = 'btn btn--' | append: style | append: ' btn--' | append: size
|
||||
if class
|
||||
assign btn_class = btn_class | append: ' ' | append: class
|
||||
endif
|
||||
-%}
|
||||
|
||||
{%- if url != blank -%}
|
||||
<a href="{{ url }}" class="{{ btn_class }}">
|
||||
{{ text | escape }}
|
||||
</a>
|
||||
{%- else -%}
|
||||
<button type="button" class="{{ btn_class }}">
|
||||
{{ text | escape }}
|
||||
</button>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 3. Icon (SVG)
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Icon
|
||||
Description: Renders an SVG icon
|
||||
|
||||
Parameters:
|
||||
- name {string} - Icon name (e.g., 'cart', 'heart', 'search') [required]
|
||||
- size {number} - Icon size in pixels (default: 24) [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'icon', name: 'cart', size: 20, class: 'icon-primary' %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign size = size | default: 24
|
||||
assign icon_class = 'icon icon-' | append: name
|
||||
if class
|
||||
assign icon_class = icon_class | append: ' ' | append: class
|
||||
endif
|
||||
-%}
|
||||
|
||||
<svg class="{{ icon_class }}" width="{{ size }}" height="{{ size }}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||
{%- case name -%}
|
||||
{%- when 'cart' -%}
|
||||
<path d="M9 2L8 4H4C2.9 4 2 4.9 2 6V18C2 19.1 2.9 20 4 20H20C21.1 20 22 19.1 22 18V6C22 4.9 21.1 4 20 4H16L15 2H9Z" stroke="currentColor" stroke-width="2"/>
|
||||
{%- when 'heart' -%}
|
||||
<path d="M20.84 4.61C19.91 3.68 18.69 3.17 17.42 3.17C16.15 3.17 14.93 3.68 14 4.61L12 6.61L10 4.61C9.07 3.68 7.85 3.17 6.58 3.17C5.31 3.17 4.09 3.68 3.16 4.61C1.22 6.55 1.22 9.69 3.16 11.63L12 20.47L20.84 11.63C22.78 9.69 22.78 6.55 20.84 4.61Z" stroke="currentColor" stroke-width="2"/>
|
||||
{%- when 'search' -%}
|
||||
<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M21 21L16.65 16.65" stroke="currentColor" stroke-width="2"/>
|
||||
{%- when 'close' -%}
|
||||
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2"/>
|
||||
{%- else -%}
|
||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
||||
{%- endcase -%}
|
||||
</svg>
|
||||
```
|
||||
|
||||
### 4. Form Input
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Form Input
|
||||
Description: Renders a form input field with label
|
||||
|
||||
Parameters:
|
||||
- type {string} - Input type (text, email, tel, number, etc.) [required]
|
||||
- name {string} - Input name attribute [required]
|
||||
- label {string} - Input label [required]
|
||||
- required {boolean} - Make field required (default: false) [optional]
|
||||
- placeholder {string} - Placeholder text [optional]
|
||||
- value {string} - Default value [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'form-input', type: 'email', name: 'email', label: 'Email Address', required: true %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign required = required | default: false
|
||||
assign input_id = 'input-' | append: name
|
||||
-%}
|
||||
|
||||
<div class="form-field {{ class }}">
|
||||
<label for="{{ input_id }}" class="form-field__label">
|
||||
{{ label | escape }}
|
||||
{%- if required -%}
|
||||
<span class="form-field__required" aria-label="required">*</span>
|
||||
{%- endif -%}
|
||||
</label>
|
||||
|
||||
<input
|
||||
type="{{ type }}"
|
||||
id="{{ input_id }}"
|
||||
name="{{ name }}"
|
||||
class="form-field__input"
|
||||
{%- if placeholder -%}placeholder="{{ placeholder | escape }}"{%- endif -%}
|
||||
{%- if value -%}value="{{ value | escape }}"{%- endif -%}
|
||||
{%- if required -%}required{%- endif -%}
|
||||
{%- if type == 'email' -%}autocomplete="email"{%- endif -%}
|
||||
>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 5. Image with Fallback
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Responsive Image
|
||||
Description: Renders a responsive image with fallback
|
||||
|
||||
Parameters:
|
||||
- image {image} - The image object [required]
|
||||
- width {number} - Image width (default: 800) [optional]
|
||||
- height {number} - Image height [optional]
|
||||
- alt {string} - Alt text (uses image.alt if not provided) [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
- loading {string} - Loading attribute: 'lazy' or 'eager' (default: 'lazy') [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'image', image: product.featured_image, width: 600, alt: product.title %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign width = width | default: 800
|
||||
assign loading = loading | default: 'lazy'
|
||||
assign alt_text = alt | default: image.alt
|
||||
-%}
|
||||
|
||||
{%- if image != blank -%}
|
||||
{%- if height -%}
|
||||
<img
|
||||
src="{{ image | image_url: width: width, height: height }}"
|
||||
alt="{{ alt_text | escape }}"
|
||||
width="{{ width }}"
|
||||
height="{{ height }}"
|
||||
loading="{{ loading }}"
|
||||
class="{{ class }}"
|
||||
>
|
||||
{%- else -%}
|
||||
<img
|
||||
srcset="
|
||||
{{ image | image_url: width: 400 }} 400w,
|
||||
{{ image | image_url: width: 800 }} 800w,
|
||||
{{ image | image_url: width: 1200 }} 1200w
|
||||
"
|
||||
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||
src="{{ image | image_url: width: width }}"
|
||||
alt="{{ alt_text | escape }}"
|
||||
width="{{ width }}"
|
||||
height="{{ width | divided_by: image.aspect_ratio | ceil }}"
|
||||
loading="{{ loading }}"
|
||||
class="{{ class }}"
|
||||
>
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
<div class="image-placeholder {{ class }}" role="img" aria-label="No image available"></div>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 6. Price Display
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Price
|
||||
Description: Displays product price with sale indication
|
||||
|
||||
Parameters:
|
||||
- product {product} - The product object [required]
|
||||
- show_compare {boolean} - Show compare at price (default: true) [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'price', product: product %}
|
||||
{% render 'price', product: product, show_compare: false %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign show_compare = show_compare | default: true
|
||||
assign on_sale = false
|
||||
if product.compare_at_price > product.price
|
||||
assign on_sale = true
|
||||
endif
|
||||
-%}
|
||||
|
||||
<div class="price {{ class }}{% if on_sale %} price--on-sale{% endif %}">
|
||||
{%- if on_sale -%}
|
||||
<span class="price__sale">{{ product.price | money }}</span>
|
||||
{%- if show_compare -%}
|
||||
<span class="price__compare">{{ product.compare_at_price | money }}</span>
|
||||
{%- endif -%}
|
||||
{%- else -%}
|
||||
<span class="price__regular">{{ product.price | money }}</span>
|
||||
{%- endif -%}
|
||||
</div>
|
||||
```
|
||||
|
||||
### 7. Badge / Tag
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Badge
|
||||
Description: Displays a badge or tag
|
||||
|
||||
Parameters:
|
||||
- text {string} - Badge text [required]
|
||||
- style {string} - Badge style: 'sale', 'new', 'featured', 'default' (default: 'default') [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'badge', text: 'Sale', style: 'sale' %}
|
||||
{% render 'badge', text: 'New Arrival', style: 'new' %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign style = style | default: 'default'
|
||||
assign badge_class = 'badge badge--' | append: style
|
||||
if class
|
||||
assign badge_class = badge_class | append: ' ' | append: class
|
||||
endif
|
||||
-%}
|
||||
|
||||
{%- if text != blank -%}
|
||||
<span class="{{ badge_class }}">
|
||||
{{ text | escape }}
|
||||
</span>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 8. Loading Spinner
|
||||
|
||||
```liquid
|
||||
{% comment %}
|
||||
Snippet: Loading Spinner
|
||||
Description: Displays a loading spinner
|
||||
|
||||
Parameters:
|
||||
- size {string} - Spinner size: 'small', 'medium', 'large' (default: 'medium') [optional]
|
||||
- class {string} - Additional CSS classes [optional]
|
||||
|
||||
Usage:
|
||||
{% render 'loading-spinner', size: 'large' %}
|
||||
{% endcomment %}
|
||||
|
||||
{%- liquid
|
||||
assign size = size | default: 'medium'
|
||||
assign spinner_class = 'spinner spinner--' | append: size
|
||||
if class
|
||||
assign spinner_class = spinner_class | append: ' ' | append: class
|
||||
endif
|
||||
-%}
|
||||
|
||||
<div class="{{ spinner_class }}" role="status" aria-label="Loading">
|
||||
<svg class="spinner__svg" viewBox="0 0 50 50">
|
||||
<circle class="spinner__circle" cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
|
||||
</svg>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Document Parameters
|
||||
```liquid
|
||||
{% comment %}
|
||||
Parameters:
|
||||
- param {type} - Description [required/optional]
|
||||
{% endcomment %}
|
||||
```
|
||||
|
||||
### 2. Provide Defaults
|
||||
```liquid
|
||||
{%- liquid
|
||||
assign show_vendor = show_vendor | default: false
|
||||
assign image_size = image_size | default: 400
|
||||
-%}
|
||||
```
|
||||
|
||||
### 3. Validate Required Parameters
|
||||
```liquid
|
||||
{%- if product == blank -%}
|
||||
<p class="error">Product parameter is required</p>
|
||||
{%- return -%}
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 4. Handle Empty States
|
||||
```liquid
|
||||
{%- if image != blank -%}
|
||||
<!-- Image markup -->
|
||||
{%- else -%}
|
||||
<div class="placeholder">No image available</div>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### 5. Escape User-Provided Content
|
||||
```liquid
|
||||
<h3>{{ product.title | escape }}</h3>
|
||||
<p>{{ customer.name | escape }}</p>
|
||||
```
|
||||
|
||||
### 6. Use Semantic HTML
|
||||
```liquid
|
||||
<article> {%- # For products -%}
|
||||
<nav> {%- # For navigation -%}
|
||||
<aside> {%- # For sidebars -%}
|
||||
<section> {%- # For content sections -%}
|
||||
```
|
||||
|
||||
### 7. Include Accessibility Attributes
|
||||
```liquid
|
||||
<button aria-label="Add {{ product.title | escape }} to cart">
|
||||
Add to Cart
|
||||
</button>
|
||||
|
||||
<img src="..." alt="{{ image.alt | escape }}">
|
||||
|
||||
<div role="status" aria-live="polite">
|
||||
Item added to cart
|
||||
</div>
|
||||
```
|
||||
|
||||
Create well-documented, reusable snippets that are easy to understand and maintain.
|
||||
417
skills/shopify-workflow-tools/SKILL.md
Normal file
417
skills/shopify-workflow-tools/SKILL.md
Normal file
@@ -0,0 +1,417 @@
|
||||
---
|
||||
name: Shopify Workflow & Tools
|
||||
description: Shopify CLI commands, development workflow, and essential tools for theme development
|
||||
---
|
||||
|
||||
# Shopify Workflow & Tools
|
||||
|
||||
## Shopify CLI Commands
|
||||
|
||||
### Initialize a New Theme
|
||||
```bash
|
||||
# Clone the Dawn reference theme
|
||||
shopify theme init
|
||||
|
||||
# Clone a specific theme
|
||||
shopify theme init --clone-url https://github.com/Shopify/dawn.git
|
||||
```
|
||||
|
||||
### Development Server
|
||||
```bash
|
||||
# Start local development server with hot-reload
|
||||
shopify theme dev --store=example.myshopify.com
|
||||
|
||||
# Development server options
|
||||
shopify theme dev --store=example.myshopify.com --port=9293
|
||||
shopify theme dev --store=example.myshopify.com --theme-editor-sync
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Hot-reload for CSS and sections
|
||||
- Live preview at `http://127.0.0.1:9292`
|
||||
- Syncs with theme editor changes
|
||||
- Chrome browser recommended
|
||||
|
||||
### Push Theme to Shopify
|
||||
```bash
|
||||
# Push as unpublished theme (recommended first push)
|
||||
shopify theme push --unpublished
|
||||
|
||||
# Push to specific theme
|
||||
shopify theme push --theme=THEME_ID
|
||||
|
||||
# Push with development flag
|
||||
shopify theme push --development
|
||||
```
|
||||
|
||||
### Pull Theme from Shopify
|
||||
```bash
|
||||
# Pull published theme
|
||||
shopify theme pull
|
||||
|
||||
# Pull specific theme
|
||||
shopify theme pull --theme=THEME_ID
|
||||
|
||||
# Pull only specific files
|
||||
shopify theme pull --only=templates/*.json,sections/*.liquid
|
||||
```
|
||||
|
||||
### Publish Theme
|
||||
```bash
|
||||
# Publish theme
|
||||
shopify theme publish --theme=THEME_ID
|
||||
|
||||
# List all themes
|
||||
shopify theme list
|
||||
```
|
||||
|
||||
### Delete Theme
|
||||
```bash
|
||||
shopify theme delete --theme=THEME_ID
|
||||
```
|
||||
|
||||
### Check Theme for Issues
|
||||
```bash
|
||||
# Run theme check
|
||||
shopify theme check
|
||||
|
||||
# Auto-fix issues
|
||||
shopify theme check --auto-correct
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Initial Setup
|
||||
```bash
|
||||
# Clone starter theme
|
||||
shopify theme init
|
||||
|
||||
# Navigate to theme directory
|
||||
cd your-theme-name
|
||||
|
||||
# Install dependencies (if using package.json)
|
||||
npm install
|
||||
|
||||
# Start development
|
||||
shopify theme dev --store=your-store.myshopify.com
|
||||
```
|
||||
|
||||
### 2. Local Development
|
||||
```bash
|
||||
# Start dev server
|
||||
shopify theme dev --store=example.myshopify.com
|
||||
|
||||
# Open browser to http://127.0.0.1:9292
|
||||
# Make changes to Liquid, CSS, JavaScript
|
||||
# See changes instantly (hot-reload for CSS/sections)
|
||||
```
|
||||
|
||||
### 3. Testing & Validation
|
||||
```bash
|
||||
# Check for theme issues
|
||||
shopify theme check
|
||||
|
||||
# Fix auto-fixable issues
|
||||
shopify theme check --auto-correct
|
||||
```
|
||||
|
||||
### 4. Deploy to Shopify
|
||||
```bash
|
||||
# First deployment (as unpublished)
|
||||
shopify theme push --unpublished
|
||||
|
||||
# Or push to development theme
|
||||
shopify theme push --development
|
||||
|
||||
# Verify in Shopify admin
|
||||
# Theme Library > View theme
|
||||
```
|
||||
|
||||
### 5. Publishing
|
||||
```bash
|
||||
# List themes to get THEME_ID
|
||||
shopify theme list
|
||||
|
||||
# Publish theme
|
||||
shopify theme publish --theme=THEME_ID
|
||||
```
|
||||
|
||||
## Theme Check
|
||||
|
||||
Shopify's official linting tool for themes.
|
||||
|
||||
### Install
|
||||
```bash
|
||||
npm install -g @shopify/theme-check
|
||||
```
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
# Run checks
|
||||
theme-check .
|
||||
|
||||
# Auto-fix issues
|
||||
theme-check . --auto-correct
|
||||
|
||||
# Check specific files
|
||||
theme-check sections/header.liquid
|
||||
```
|
||||
|
||||
### Common Checks
|
||||
- **Liquid syntax errors**
|
||||
- **Performance issues**
|
||||
- **Accessibility problems**
|
||||
- **Deprecated tags/filters**
|
||||
- **Missing translations**
|
||||
- **Asset optimization**
|
||||
|
||||
### Integration with VS Code
|
||||
Install the "Shopify Liquid" extension for:
|
||||
- Real-time linting
|
||||
- Syntax highlighting
|
||||
- Autocomplete
|
||||
- Hover documentation
|
||||
|
||||
## File Structure
|
||||
|
||||
### Required Files
|
||||
```
|
||||
your-theme/
|
||||
├── config/
|
||||
│ ├── settings_schema.json # Global theme settings (required)
|
||||
│ └── settings_data.json # Theme setting values (auto-generated)
|
||||
├── layout/
|
||||
│ └── theme.liquid # Base layout (required)
|
||||
├── templates/
|
||||
│ ├── index.json # Homepage template (required)
|
||||
│ ├── product.json # Product page template
|
||||
│ ├── collection.json # Collection page template
|
||||
│ └── 404.liquid # 404 page
|
||||
├── sections/
|
||||
│ └── *.liquid # Reusable sections
|
||||
├── snippets/
|
||||
│ └── *.liquid # Reusable code fragments
|
||||
├── assets/
|
||||
│ └── *.* # CSS, JS, images, fonts
|
||||
└── locales/
|
||||
└── en.default.json # Default language (required)
|
||||
```
|
||||
|
||||
## Environment Best Practices
|
||||
|
||||
### Development Stores
|
||||
- Free for Partners
|
||||
- Sandbox for testing
|
||||
- No impact on production
|
||||
- Can transfer to client
|
||||
|
||||
```bash
|
||||
# Create development store
|
||||
# Visit partners.shopify.com
|
||||
# Stores > Add store > Development store
|
||||
```
|
||||
|
||||
### Theme Environments
|
||||
- **Development**: `shopify theme dev` or `--development` flag
|
||||
- **Staging/Preview**: `--unpublished` flag
|
||||
- **Production**: Published theme
|
||||
|
||||
### Version Control
|
||||
```bash
|
||||
# Initialize git
|
||||
git init
|
||||
|
||||
# Add .gitignore
|
||||
echo "config/settings_data.json" >> .gitignore
|
||||
echo "node_modules/" >> .gitignore
|
||||
echo ".DS_Store" >> .gitignore
|
||||
|
||||
# Commit theme
|
||||
git add .
|
||||
git commit -m "Initial theme commit"
|
||||
```
|
||||
|
||||
**Important**: `.gitignore` should exclude:
|
||||
- `config/settings_data.json` (store-specific)
|
||||
- `node_modules/` (dependencies)
|
||||
- `.DS_Store` (macOS files)
|
||||
|
||||
## Debugging Techniques
|
||||
|
||||
### Liquid Debugging
|
||||
```liquid
|
||||
{%- comment -%}
|
||||
Debug output using assign and display
|
||||
{%- endcomment -%}
|
||||
|
||||
{%- assign debug = true -%}
|
||||
|
||||
{%- if debug -%}
|
||||
<pre>
|
||||
Product: {{ product | json }}
|
||||
Cart: {{ cart | json }}
|
||||
</pre>
|
||||
{%- endif -%}
|
||||
```
|
||||
|
||||
### Console Logging
|
||||
```liquid
|
||||
{% javascript %}
|
||||
console.log('Product data:', {{ product | json }});
|
||||
console.log('Settings:', {{ section.settings | json }});
|
||||
{% endjavascript %}
|
||||
```
|
||||
|
||||
### Theme Check Output
|
||||
```bash
|
||||
# Verbose output
|
||||
shopify theme check --verbose
|
||||
|
||||
# Output to file
|
||||
shopify theme check > check-results.txt
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Image Optimization
|
||||
```liquid
|
||||
{%- # Use appropriate image sizes -%}
|
||||
<img
|
||||
srcset="
|
||||
{{ image | image_url: width: 400 }} 400w,
|
||||
{{ image | image_url: width: 800 }} 800w,
|
||||
{{ image | image_url: width: 1200 }} 1200w
|
||||
"
|
||||
sizes="(min-width: 1024px) 33vw, (min-width: 768px) 50vw, 100vw"
|
||||
src="{{ image | image_url: width: 800 }}"
|
||||
loading="lazy"
|
||||
>
|
||||
```
|
||||
|
||||
### Asset Loading
|
||||
```liquid
|
||||
{%- # Defer non-critical JavaScript -%}
|
||||
<script src="{{ 'theme.js' | asset_url }}" defer></script>
|
||||
|
||||
{%- # Preload critical assets -%}
|
||||
<link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style">
|
||||
```
|
||||
|
||||
### Minimize Liquid Logic
|
||||
```liquid
|
||||
{%- # Good - assign complex logic to variable -%}
|
||||
{%- liquid
|
||||
assign is_on_sale = false
|
||||
if product.compare_at_price > product.price
|
||||
assign is_on_sale = true
|
||||
endif
|
||||
-%}
|
||||
|
||||
{%- if is_on_sale -%}
|
||||
<span>On Sale</span>
|
||||
{%- endif -%}
|
||||
|
||||
{%- # Bad - inline complex logic -%}
|
||||
{% if product.compare_at_price > product.price %}
|
||||
<span>On Sale</span>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
```yaml
|
||||
name: Deploy Theme
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Install Shopify CLI
|
||||
run: npm install -g @shopify/cli @shopify/theme
|
||||
- name: Push theme
|
||||
run: shopify theme push --development
|
||||
env:
|
||||
SHOPIFY_CLI_THEME_TOKEN: ${{ secrets.SHOPIFY_CLI_THEME_TOKEN }}
|
||||
```
|
||||
|
||||
## Common Commands Reference
|
||||
|
||||
```bash
|
||||
# Authentication
|
||||
shopify auth login
|
||||
|
||||
# Theme commands
|
||||
shopify theme list # List all themes
|
||||
shopify theme info # Show theme info
|
||||
shopify theme dev # Start dev server
|
||||
shopify theme push # Push theme
|
||||
shopify theme pull # Pull theme
|
||||
shopify theme publish # Publish theme
|
||||
shopify theme delete # Delete theme
|
||||
shopify theme check # Check theme for issues
|
||||
shopify theme package # Package theme as .zip
|
||||
|
||||
# Theme check
|
||||
theme-check . # Run checks
|
||||
theme-check . --auto-correct # Fix issues
|
||||
theme-check . --config=.theme-check.yml # Use custom config
|
||||
|
||||
# Help
|
||||
shopify theme --help # Show help
|
||||
shopify theme dev --help # Show dev command help
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Development Server Not Starting
|
||||
```bash
|
||||
# Check if port is in use
|
||||
lsof -i :9292
|
||||
|
||||
# Use different port
|
||||
shopify theme dev --port=9293
|
||||
```
|
||||
|
||||
### Authentication Issues
|
||||
```bash
|
||||
# Re-authenticate
|
||||
shopify auth logout
|
||||
shopify auth login
|
||||
```
|
||||
|
||||
### Theme Push Conflicts
|
||||
```bash
|
||||
# Force push (caution: overwrites remote)
|
||||
shopify theme push --force
|
||||
|
||||
# Pull first, then push
|
||||
shopify theme pull
|
||||
shopify theme push
|
||||
```
|
||||
|
||||
### Hot Reload Not Working
|
||||
- Ensure using Chrome browser
|
||||
- Check file paths are correct
|
||||
- Restart dev server
|
||||
- Clear browser cache
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use development stores** for testing
|
||||
2. **Push unpublished first** before publishing
|
||||
3. **Run theme check** before deploying
|
||||
4. **Use version control** (Git)
|
||||
5. **Exclude settings_data.json** from Git
|
||||
6. **Test on multiple devices** before publishing
|
||||
7. **Keep CLI updated**: `npm update -g @shopify/cli`
|
||||
8. **Use theme editor sync** during development
|
||||
9. **Document custom features** in README
|
||||
10. **Follow Shopify theme requirements** for Theme Store
|
||||
|
||||
Use these tools and workflows to build, test, and deploy Shopify themes efficiently.
|
||||
Reference in New Issue
Block a user