shopify-theme-dev
Shopify Theme Development
Expert guidance for Shopify theme development including file structure, Online Store 2.0 architecture, sections, snippets, and configuration.
When to Use This Skill
Invoke this skill when:
- Creating or modifying Shopify themes
- Working with
.jsontemplate files (Online Store 2.0) - Building theme sections with schema definitions
- Creating reusable snippets
- Organizing theme file structure
- Configuring
settings_schema.jsonfor theme settings - Implementing section blocks and settings
- Setting up theme assets (CSS, JavaScript, images)
- Working with layout files (
theme.liquid,password.liquid) - Creating template variations with suffixes
Core Capabilities
1. Theme File Structure
Complete directory organization for Shopify themes:
theme/
├── assets/ {# Static resources #}
│ ├── style.css {# Main stylesheet #}
│ ├── style.css.liquid {# Dynamic CSS with Liquid #}
│ ├── theme.js {# Main JavaScript #}
│ ├── theme.js.liquid {# Dynamic JS with Liquid #}
│ ├── logo.png {# Images #}
│ └── fonts/ {# Custom fonts #}
│
├── config/ {# Configuration #}
│ ├── settings_schema.json {# Theme settings UI #}
│ └── settings_data.json {# Default values #}
│
├── layout/ {# Master templates #}
│ ├── theme.liquid {# Main wrapper #}
│ ├── password.liquid {# Password protection #}
│ └── checkout.liquid {# Checkout (Plus only) #}
│
├── locales/ {# Translations #}
│ ├── en.default.json {# English #}
│ └── fr.json {# French #}
│
├── sections/ {# Reusable sections #}
│ ├── header.liquid
│ ├── hero-banner.liquid
│ ├── product-card.liquid
│ └── footer.liquid
│
├── snippets/ {# Reusable partials #}
│ ├── product-price.liquid
│ ├── product-rating.liquid
│ └── icon.liquid
│
└── templates/ {# Page templates #}
├── index.json {# Homepage (JSON) #}
├── product.json {# Product page (JSON) #}
├── collection.json {# Collection page (JSON) #}
├── product.liquid {# Product (Liquid - legacy) #}
├── cart.liquid
├── search.liquid
├── page.liquid
├── 404.liquid
└── customers/
├── account.liquid
├── login.liquid
└── register.liquid
2. JSON Templates (Online Store 2.0)
Modern template format using JSON configuration:
templates/index.json (Homepage):
{
"sections": {
"hero": {
"type": "hero-banner",
"settings": {
"heading": "Summer Collection",
"subheading": "New arrivals",
"button_text": "Shop Now",
"button_link": "/collections/all"
}
},
"featured": {
"type": "featured-products",
"blocks": {
"block_1": {
"type": "product",
"settings": {
"product": "snowboard"
}
},
"block_2": {
"type": "product",
"settings": {
"product": "skateboard"
}
}
},
"block_order": ["block_1", "block_2"],
"settings": {
"title": "Featured Products",
"products_to_show": 4
}
}
},
"order": ["hero", "featured"]
}
templates/product.json:
{
"sections": {
"main": {
"type": "main-product",
"settings": {
"show_vendor": true,
"show_quantity": true,
"enable_zoom": true
}
},
"recommendations": {
"type": "product-recommendations",
"settings": {
"heading": "You may also like",
"products_to_show": 4
}
}
},
"order": ["main", "recommendations"]
}
3. Section Architecture
Sections are reusable content blocks with schema configuration:
sections/hero-banner.liquid:
<div class="hero" style="background-color: {{ section.settings.background_color }}">
{% if section.settings.image %}
<img
src="{{ section.settings.image | img_url: '1920x' }}"
alt="{{ section.settings.heading }}"
loading="lazy"
>
{% endif %}
<div class="hero__content">
{% if section.settings.heading != blank %}
<h1>{{ section.settings.heading }}</h1>
{% endif %}
{% if section.settings.subheading != blank %}
<p>{{ section.settings.subheading }}</p>
{% endif %}
{% if section.settings.button_text != blank %}
<a href="{{ section.settings.button_link }}" class="button">
{{ section.settings.button_text }}
</a>
{% endif %}
</div>
</div>
{% stylesheet %}
.hero {
position: relative;
min-height: 500px;
display: flex;
align-items: center;
justify-content: center;
padding: var(--spacing, 2rem);
}
.hero img {
position: absolute;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
}
.hero__content {
text-align: center;
max-width: 600px;
}
{% endstylesheet %}
{% javascript %}
console.log('Hero banner loaded');
{% endjavascript %}
{% schema %}
{
"name": "Hero Banner",
"tag": "section",
"class": "hero-section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Welcome"
},
{
"type": "textarea",
"id": "subheading",
"label": "Subheading",
"default": "Discover our collection"
},
{
"type": "image_picker",
"id": "image",
"label": "Background Image"
},
{
"type": "color",
"id": "background_color",
"label": "Background Color",
"default": "#000000"
},
{
"type": "text",
"id": "button_text",
"label": "Button Text",
"default": "Shop Now"
},
{
"type": "url",
"id": "button_link",
"label": "Button Link"
}
],
"presets": [
{
"name": "Hero Banner"
}
]
}
{% endschema %}
4. Sections with Blocks
Sections can contain dynamic blocks for flexible layouts:
sections/featured-products.liquid:
<div class="featured-products" {{ section.shopify_attributes }}>
<h2>{{ section.settings.title }}</h2>
<div class="product-grid">
{% for block in section.blocks %}
<div class="product-item" {{ block.shopify_attributes }}>
{% case block.type %}
{% when 'product' %}
{% assign product = all_products[block.settings.product] %}
{% render 'product-card', product: product %}
{% when 'collection' %}
{% assign collection = collections[block.settings.collection] %}
<h3>{{ collection.title }}</h3>
{% for product in collection.products limit: block.settings.products_to_show %}
{% render 'product-card', product: product %}
{% endfor %}
{% when 'heading' %}
<h3>{{ block.settings.heading }}</h3>
{% when 'text' %}
<div class="text-block">
{{ block.settings.text }}
</div>
{% endcase %}
</div>
{% endfor %}
</div>
</div>
{% schema %}
{
"name": "Featured Products",
"tag": "section",
"settings": [
{
"type": "text",
"id": "title",
"label": "Section Title",
"default": "Featured Products"
},
{
"type": "range",
"id": "products_per_row",
"label": "Products per Row",
"min": 2,
"max": 5,
"step": 1,
"default": 4
}
],
"blocks": [
{
"type": "product",
"name": "Product",
"settings": [
{
"type": "product",
"id": "product",
"label": "Product"
}
]
},
{
"type": "collection",
"name": "Collection",
"settings": [
{
"type": "collection",
"id": "collection",
"label": "Collection"
},
{
"type": "range",
"id": "products_to_show",
"label": "Products to Show",
"min": 1,
"max": 12,
"step": 1,
"default": 4
}
]
},
{
"type": "heading",
"name": "Heading",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading Text"
}
]
},
{
"type": "text",
"name": "Text Block",
"settings": [
{
"type": "richtext",
"id": "text",
"label": "Text Content"
}
]
}
],
"presets": [
{
"name": "Featured Products",
"blocks": [
{
"type": "product"
},
{
"type": "product"
},
{
"type": "product"
}
]
}
],
"max_blocks": 12
}
{% endschema %}
5. Snippets
Reusable template partials:
snippets/product-card.liquid:
{% comment %}
Usage: {% render 'product-card', product: product, show_vendor: true %}
{% endcomment %}
<div class="product-card">
<a href="{{ product.url }}">
{% if product.featured_image %}
<img
src="{{ product.featured_image | img_url: '400x400' }}"
alt="{{ product.featured_image.alt | escape }}"
loading="lazy"
>
{% else %}
{{ 'product-1' | placeholder_svg_tag: 'placeholder' }}
{% endif %}
</a>
<div class="product-card__info">
{% if show_vendor and product.vendor != blank %}
<p class="product-card__vendor">{{ product.vendor }}</p>
{% endif %}
<h3 class="product-card__title">
<a href="{{ product.url }}">{{ product.title }}</a>
</h3>
<div class="product-card__price">
{% render 'product-price', product: product %}
</div>
{% unless product.available %}
<p class="sold-out">Sold Out</p>
{% endunless %}
</div>
</div>
snippets/product-price.liquid:
{% comment %}
Usage: {% render 'product-price', product: product %}
{% endcomment %}
{% if product.compare_at_price > product.price %}
<span class="price price--sale">
{{ product.price | money }}
</span>
<span class="price price--compare">
{{ product.compare_at_price | money }}
</span>
<span class="price__badge">
Save {{ product.compare_at_price | minus: product.price | money }}
</span>
{% else %}
<span class="price">
{{ product.price | money }}
</span>
{% endif %}
{% if product.price_varies %}
<span class="price__from">from</span>
{% endif %}
6. Settings Schema
Complete theme customization interface:
config/settings_schema.json:
[
{
"name": "theme_info",
"theme_name": "My Theme",
"theme_version": "1.0.0",
"theme_author": "Your Name",
"theme_documentation_url": "https://...",
"theme_support_url": "https://..."
},
{
"name": "Colors",
"settings": [
{
"type": "header",
"content": "Color Scheme"
},
{
"type": "color",
"id": "color_primary",
"label": "Primary Color",
"default": "#000000"
},
{
"type": "color",
"id": "color_secondary",
"label": "Secondary Color",
"default": "#ffffff"
},
{
"type": "color_background",
"id": "color_body_bg",
"label": "Body Background"
}
]
},
{
"name": "Typography",
"settings": [
{
"type": "font_picker",
"id": "type_header_font",
"label": "Heading Font",
"default": "helvetica_n7"
},
{
"type": "font_picker",
"id": "type_body_font",
"label": "Body Font",
"default": "helvetica_n4"
},
{
"type": "range",
"id": "type_base_size",
"label": "Base Font Size",
"min": 12,
"max": 24,
"step": 1,
"default": 16,
"unit": "px"
}
]
},
{
"name": "Layout",
"settings": [
{
"type": "select",
"id": "layout_style",
"label": "Layout Style",
"options": [
{ "value": "boxed", "label": "Boxed" },
{ "value": "full-width", "label": "Full Width" },
{ "value": "wide", "label": "Wide" }
],
"default": "full-width"
},
{
"type": "checkbox",
"id": "layout_sidebar_enabled",
"label": "Enable Sidebar",
"default": true
}
]
},
{
"name": "Header",
"settings": [
{
"type": "image_picker",
"id": "logo",
"label": "Logo"
},
{
"type": "range",
"id": "logo_max_width",
"label": "Logo Width",
"min": 50,
"max": 300,
"step": 10,
"default": 150,
"unit": "px"
},
{
"type": "link_list",
"id": "main_menu",
"label": "Main Menu"
},
{
"type": "checkbox",
"id": "header_sticky",
"label": "Sticky Header",
"default": false
}
]
},
{
"name": "Social Media",
"settings": [
{
"type": "header",
"content": "Social Accounts"
},
{
"type": "url",
"id": "social_twitter",
"label": "Twitter URL",
"info": "https://twitter.com/username"
},
{
"type": "url",
"id": "social_facebook",
"label": "Facebook URL"
},
{
"type": "url",
"id": "social_instagram",
"label": "Instagram URL"
}
]
}
]
7. Layout Files
Master template wrappers:
layout/theme.liquid:
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>
{{ page_title }}
{%- if current_tags %} – {{ 'general.meta.tags' | t: tags: current_tags.join(', ') }}{% endif -%}
{%- if current_page != 1 %} – {{ 'general.meta.page' | t: page: current_page }}{% endif -%}
{%- unless page_title contains shop.name %} – {{ shop.name }}{% endunless -%}
</title>
{{ content_for_header }}
<link rel="stylesheet" href="{{ 'style.css' | asset_url }}">
<script src="{{ 'theme.js' | asset_url }}" defer></script>
</head>
<body class="template-{{ request.page_type }}">
{% section 'header' %}
<main role="main">
{{ content_for_layout }}
</main>
{% section 'footer' %}
</body>
</html>
Common Settings Input Types
All 28+ input types for theme customization:
text- Single line texttextarea- Multi-line texthtml- HTML editorrichtext- WYSIWYG editornumber- Numeric inputrange- Slidercheckbox- Boolean toggleselect- Dropdown menuradio- Radio buttonscolor- Color pickercolor_background- Color with gradientimage_picker- Upload imagemedia- Image or videourl- URL inputfont_picker- Font selectorproduct- Product pickercollection- Collection pickerpage- Page pickerblog- Blog pickerarticle- Article pickerlink_list- Menu pickerdate- Date pickervideo_url- Video URL (YouTube, Vimeo)
See references/settings-schema.md for complete examples.
Best Practices
- Use JSON templates for Online Store 2.0 compatibility
- Make sections dynamic with blocks for merchant flexibility
- Add
shopify_attributesto section/block containers for theme editor - Provide sensible defaults in schema settings
- Use snippets for repeated UI components
- Add
{% stylesheet %}and{% javascript %}blocks in sections for scoped styles - Include accessibility attributes (ARIA labels, alt text)
- Test in theme editor to ensure live preview works
- Document snippet parameters with comments
- Use semantic HTML for better SEO
Detailed References
- references/settings-schema.md - Complete input type reference
- references/section-patterns.md - Common section architectures
- references/template-examples.md - JSON template patterns
Integration with Other Skills
- shopify-liquid - Use when working with Liquid code within theme files
- shopify-performance - Use when optimizing theme load times and asset delivery
- shopify-api - Use when fetching data via Ajax for dynamic sections
- shopify-debugging - Use when troubleshooting theme editor or rendering issues
Quick Reference
{# Section with settings #}
{% schema %}
{
"name": "Section Name",
"settings": [...],
"blocks": [...],
"presets": [...]
}
{% endschema %}
{# Access section settings #}
{{ section.settings.setting_id }}
{# Loop through blocks #}
{% for block in section.blocks %}
{{ block.settings.text }}
{{ block.shopify_attributes }}
{% endfor %}
{# Render snippet with parameters #}
{% render 'snippet-name', param: value %}
{# Access theme settings #}
{{ settings.color_primary }}
{# Section attributes for theme editor #}
<div {{ section.shopify_attributes }}>...</div>
<div {{ block.shopify_attributes }}>...</div>