Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:54:05 +08:00
commit 2f6a53cffa
14 changed files with 5005 additions and 0 deletions

View 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.

View 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 }} # &lt;p&gt;test&lt;/p&gt;
{{ '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.

View 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.

View 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.

View 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.

View 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.