--- 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 -%}
{%- if heading != blank -%}

{{ heading | escape }}

{%- endif -%} {%- comment -%} Section content {%- endcomment -%}
{% 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 -%}
{%- for product in collection.products limit: products_count -%}
{%- if product.featured_image -%} {{ product.featured_image.alt | escape }} {%- endif -%}

{{ product.title | escape }}

{{ product.price | money }}

{%- endfor -%}
{% 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 -%}
{%- if image -%}
{{ image.alt | escape }}
{%- endif -%}
{%- if heading != blank -%}

{{ heading | escape }}

{%- endif -%} {%- if text != blank -%}

{{ text }}

{%- endif -%} {%- if button_label != blank and button_link != blank -%} {{ button_label | escape }} {%- endif -%}
{% 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
{%- for block in section.blocks -%}
{%- if block.settings.quote != blank -%}
{{ block.settings.quote }}
{%- endif -%} {%- if block.settings.author != blank -%} {{ block.settings.author | escape }} {%- endif -%}
{%- endfor -%}
{% 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
{% 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 -%} {%- else -%}

No products available in this collection.

{%- 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
{% 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
``` ### 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.